使用多处理交错数据加载.Queue有时会导致项目无序使用

时间:2014-10-16 16:15:17

标签: python multiprocessing python-multiprocessing

我正在编写一个动画图像数据的脚本。我有许多大型图像立方体(3D阵列)。对于其中的每一个,我逐步浏览每个立方体中的帧,一旦我接近它的末尾,我加载下一个立方体并继续。由于每个立方体的大尺寸,存在显着的加载时间(~5s)。我希望动画能够无缝地在多维数据集之间进行转换(同时也节省了内存),所以我正在错开加载过程。我在解决方案方面取得了一些进展,但仍存在一些问题。

下面的代码加载每个数据立方体,将其拆分为框架并将它们放入multiprocessing.Queue。一旦队列中的帧数低于某个阈值,就会触发下一个加载进程,加载另一个多维数据集并将其解压缩到队列中。

查看以下代码:

import numpy as np
import multiprocessing as mp
import logging
logger = mp.log_to_stderr(logging.INFO)
import time

def data_loader(event, queue, **kw):
    '''loads data from 3D image cube'''
    event.wait()        #wait for trigger before loading

    logger.info( 'Loading data' )
    time.sleep(3)                       #pretend to take long to load the data
    n = 100
    data = np.ones((n,20,20))*np.arange(n)[:,None,None]          #imaginary 3D image cube (increasing numbers so that we can track the data ordering)

    logger.info( 'Adding data to queue' )
    for d in data:
        queue.put(d)
    logger.info( 'Done adding to queue!' )


def queue_monitor(queue, triggers, threshold=50, interval=5):
    '''
    Triggers the load events once the number of data in the queue falls below 
    threshold, then doesn't trigger again until the interval has passed.  
    Note: interval should be larger than data load time.
    '''
    while len(triggers):
        if queue.qsize() < threshold:
            logger.info( 'Triggering next load' )
            triggers.pop(0).set()
            time.sleep(interval)    


if __name__ == '__main__':
    logger.info( "Starting" )
    out_queue = mp.Queue()

    #Initialise the load processes
    nprocs, procs = 3, []
    triggers = [mp.Event() for _ in range(nprocs)]
    triggers[0].set()           #set the first process to trigger immediately
    for i, trigger in enumerate(triggers):
        p = mp.Process( name='data_loader %d'%i, target=data_loader, 
                        args=(trigger, out_queue) )
        procs.append( p )
    for p in procs:
        p.start()

    #Monitoring process
    qm = mp.Process( name='queue_monitor', target=queue_monitor, 
                     args=(out_queue, triggers) )
    qm.start()

    #consume data
    while out_queue.empty():
        pass
    else:
        for d in iter( out_queue.get, None ):
            time.sleep(0.2)   #pretend to take some time to process/animate the data
            logger.info( 'data: %i' %d[0,0] )   #just to keep track of data ordering

在某些情况下,这种方法非常出色,但有时在触发新的加载过程后,数据的顺序会混乱。我无法弄清楚为什么会发生这种情况 - mp.Queue应该是FIFO吗?!例如。按原样运行上面的代码将不会在输出队列中保留正确的顺序,但是,将阈值更改为较低的值,例如。 30解决了这个问题。 *很困惑......

所以问题:如何在python中使用multiprocessing正确实现这种交错加载策略?

1 个答案:

答案 0 :(得分:3)

这看起来像一个缓冲问题。在内部,multiprocessing.Queue使用缓冲区临时存储您已入队的项目,并最终将它们刷新到后台线程中的Pipe。只有在发生冲洗之后,才会将项目实际发送到其他进程。因为您要将大型对象放到Queue上,所以会有很多缓冲。这导致加载过程实际重叠,即使您的日志记录显示一个进程在另一个进程开始之前完成。文档实际上有关于这种情况的警告:

  

当一个对象被放入队列时,该对象被腌制并且a   后台线程稍后将pickle数据刷新到底层   管。这有一些后果,但有点令人惊讶   不应该造成任何实际困难 - 如果他们真的很烦   然后,您可以使用由经理创建的队列。

     
      
  1. 将对象放在空队列后,队列的empty()方法返回False之前可能会有无穷小的延迟   并且get_nowait()可以在不提出Queue.Empty的情况下返回。
  2.   
  3. 如果多个进程将对象排入队列,则可能无序地在另一端接收对象。然而,   由相同进程排队的对象将始终处于预期状态   彼此订购。
  4.   

我建议您按照文档状态进行操作,然后使用multiprocessing.Manager创建队列:

m = mp.Manager()
out_queue = m.Queue()

这将让您完全避免这个问题。

另一种选择是只使用一个进程来完成所有数据加载,并让它在循环中运行,并在循环顶部调用event.wait()