python中使用视频帧内存缓存作为循环缓冲策略的芹菜任务设置

python中使用视频帧内存缓存作为循环缓冲策略的芹菜任务设置,python,caching,celery,shared-memory,video-processing,Python,Caching,Celery,Shared Memory,Video Processing,我想建立一个多任务处理管道上,并希望几个任务处理同一个视频文件。 任务需要共享视频数据。因此,并非每个任务都必须解码并从视频文件中提取帧。视频数据将是提取帧的列表(不需要视频的每一帧) 是否有任何解决方案可以有效地共享这些帧?可以在不同的节点上处理任务。但我不想像Memcached或Redis那样通过网络共享数据。 任务应在内存/缓存中查找视频数据,如果不在内存/缓存中,则任务应发出另一个任务以加载视频并提取要缓存的帧 (每个视频文件的制作人和多个消费者) 因此,同一节点/机器上的任务能够共享缓

我想建立一个多任务处理管道上,并希望几个任务处理同一个视频文件。 任务需要共享视频数据。因此,并非每个任务都必须解码并从视频文件中提取帧。视频数据将是提取帧的列表(不需要视频的每一帧)

是否有任何解决方案可以有效地共享这些帧?可以在不同的节点上处理任务。但我不想像Memcached或Redis那样通过网络共享数据。 任务应在内存/缓存中查找视频数据,如果不在内存/缓存中,则任务应发出另一个任务以加载视频并提取要缓存的帧

(每个视频文件的制作人和多个消费者)

因此,同一节点/机器上的任务能够共享缓存的数据。缓存对不同节点上的两个任务没有好处

我不想缓存整个提取的视频,必须有一些循环缓冲缓存。每个视频的缓存有一个固定的大小,比如说100帧。最快和最慢任务之间的间隔不能超过100帧。内存/缓存中总共只有100帧

出现了两个主要问题:

  • 任务设置

    任务A:从视频中提取帧(生产者到内存/缓存)

    任务B1:使用帧和执行实际工作(处理帧)

    任务Bn:使用帧并执行实际工作(处理帧)

    A、 B1-Bn并联运行。 但是这些任务必须在同一个节点上运行。如果B tak分布在不同的节点上,则必须生成另一个A任务(每个节点上一个任务用于解码和提取帧)。 你在这里推荐什么方法?最好的选择是什么

  • Python缓存

    是否有最适合我的使用案例的缓存库/实现/解决方案,可以使用循环缓冲区实现在本地计算机上缓存大型数据? 类似于但通过环缓冲只能缓存100帧


  • 您推荐什么方法和设计来实现我的用例?我想坚持用芹菜来分配任务。

    这可能是我的固执表现,但我总是发现芹菜这样的项目在多处理(已经很复杂)的基础上增加了一堆复杂性,这比它们的价值更麻烦。从速度和简单性的角度来看,除了使用stdlib共享内存和互斥量之外,没有更好的替代方法

    对于您的情况,一个简单的解决方案是对每个进程使用一个fifo队列,并将帧放入来自生产者的每个进程中。如果您为n个使用者制作每个帧的n个副本,这自然会产生大量内存使用,但是您可能很容易想出一种机制,将帧本身放入
    多处理.sharedTypes.Array
    中,而只通过队列传递索引。只要队列的长度限制在小于缓冲区长度的范围内,就应该限制您覆盖缓冲区中的帧,直到它被所有使用者使用为止。如果没有任何同步,这将是你的裤子座位飞行,但一点点的互斥魔术肯定可以使这是一个非常强大的解决方案

    例如:

    import numpy as np
    from time import sleep
    from multiprocessing import Process, freeze_support, Queue
    from multiprocessing.sharedctypes import Array
    from ctypes import c_uint8
    from functools import reduce
    
    BUFSHAPE = (10,10,10) #10 10x10 images in buffer
    
    class Worker(Process):
        def __init__(self, q_size, buffer, name=''):
            super().__init__()
            self.queue = Queue(q_size)
            self.buffer = buffer
            self.name = name
    
        def run(self,): #do work here
            #I hardcoded datatype here. you might need to communicate it to the child process
            buf_arr = np.frombuffer(self.buffer.get_obj(), dtype=c_uint8)
            buf_arr.shape = BUFSHAPE
            while True:
                item = self.queue.get()
                if item == 'done':
                    print('child process: {} completed all frames'.format(self.name))
                    return
                with self.buffer.get_lock(): #prevent writing while we're reading
                    #slice the frame from the array uning the index that was sent
                    frame = buf_arr[item%BUFSHAPE[0]] #depending on your use, you may want to make a copy here
                #do some intense processing on `frame`
                sleep(np.random.rand())
                print('child process: {} completed frame: {}'.format(self.name, item))
    
    def main():
        #creating shared array
        buffer = Array(c_uint8, reduce(lambda a,b: a*b, BUFSHAPE))
        #make a numpy.array using that memory location to make it easy to stuff data into it
        buf_arr = np.frombuffer(buffer.get_obj(), dtype=c_uint8)
        buf_arr.shape = BUFSHAPE
        #create a list of workers
        workers = [Worker(BUFSHAPE[0]-2, #smaller queue than buffer to prevent overwriting frames not yet consumed
                          buffer, #pass in shared buffer array
                          str(i)) #numbered child processes
                          for i in range(5)] #5 workers
    
        for worker in workers: #start the workers
            worker.start()
        for i in range(100): #generate 100 random frames to send to workers
            #insert a frame into the buffer
            with buffer.get_lock(): #prevent reading while we're writing
                buf_arr[i%BUFSHAPE[0]] = np.random.randint(0,255, size=(10,10), dtype=c_uint8)
            #send the frame number to each worker for processing. If the input queue is full, this will block until there's space
            # this is what prevents `buf_arr[i%BUFSHAPE[0]] = np...` from overwriting a frame that hasn't been processed yet
            for worker in workers:
                worker.queue.put(i)
        #when we're done send the 'done' signal so the child processes exit gracefully (or you could make them daemons)
        for worker in workers:
            worker.queue.put('done')
            worker.join()
    
    
    if __name__ == "__main__":
        freeze_support()
        main()
    
    编辑

    某种类型的“按一关闭”错误要求队列比缓冲区小2帧,而不是比缓冲区小1帧,以防止在帧到达时间之前覆盖帧

    EDIT2-第一次编辑的解释:


    len(q)=len(buf)-2
    的原因似乎是在我们从缓冲区获取帧之前调用
    q.get()
    ,并且在我们尝试将索引推送到队列之前写入帧本身。如果长度差仅为1,则工作者可能会从队列中提取帧索引,然后生产者可能会看到它现在可以推送到队列并在工作者有机会读取帧本身之前移动到下一帧。有许多不同的方法可以实现这一点,可能会允许更少的进程始终互相等待,例如使用
    mp.Event

    您希望运行缓慢的进程丢弃帧,还是希望运行快速的进程等待运行缓慢的帧?第二。在示例中,当循环缓冲区已满时,任务A将等待最慢的B任务(读取指针距离任务A的写入指针100帧)