加入多处理队列需要很长时间

时间:2017-02-20 17:32:40

标签: python queue python-multiprocessing

在Python 2.7中,我实现了一个包含多个队列和消费者的多处理场景。简化的想法是,我有一个作业生产者,它被提供给消费者,处理作业和一个错误处理程序,它完成所有日志记录。非常简化,它看起来都与之相当:

import multiprocessing as mp
import Queue

job_queue = mp.Queue()
error_queue = mp.Queue()
for i in range(10):
    job_queue.put(i)

def job_handler(job_queue, error_queue):
    print 'Job handler'
    while True:
        try: 
            element = job_queue.get_nowait()
            print element
        except:
# t1
            error_queue.put('Error')
            error_queue.close()
            error_queue.join_thread()
            job_queue.close()
            job_queue.join_thread()
# t2
            return 1

def error_handler(error_queue):
    result = error_queue.get()
    if result == 'Error':
        error_queue.close()
        error_queue.join_thread()

if __name__ == '__main__':
    print 'Starting'
    p1 = mp.Process(target = error_handler, args = (error_queue, ))
    p1.start()
    p2 = mp.Process(target = job_handler, args = (job_queue, error_queue))
    p2.start()

这基本上有效,但在我更复杂的程序中,两个评论点t1t2之间存在很长的时间差异(约5分钟)。所以我有两个问题:

  1. 我是否理解正确,每个进程都应在所有使用过的Queue对象上调用close()join_thread(),以表明它已经完成了?我认为,当我结束它们时,子进程会隐式地执行此操作,例如通过按照所述here返回:
  2.   

    join_thread()加入后台主题。这只能在以后使用   已经调用了close()。它会阻塞,直到后台线程退出,   确保缓冲区中的所有数据都已刷新到管道。

         

    默认情况下,如果进程不是队列的创建者,则退出   它将尝试加入队列的后台线程。这个过程可以   调用cancel_join_thread()使join_thread()不执行任何操作。

    1. 如何确定加入过程需要这么长时间的原因?

4 个答案:

答案 0 :(得分:1)

致电.close().join_thread()是一项建议,但不是必须的。队列被垃圾回收时会自动调用.close(),并在进程终止时自动调用.join_thread()

不幸的是,我运行了你的代码,并在5秒后打印出0-9秒后获得了漂亮的终止。即使我推了一个不可打印的角色,我也没有收到任何延迟。代码似乎很流利。

关于更复杂的程序,如果您通过队列传递大量数据,可能会发生这种情况。队列用作IPC,意味着数据在一侧编码,推入管道,在另一侧解码。传递大量数据会导致速度放缓。由于它最终会自行解决,所以它似乎并不是一个僵局。

虽然最好避免使用它,但选项是使用shared memory而不是队列。这样,数据不需要在进程之间传递,而只是保留在两者共享的一个内存段中。

答案 1 :(得分:1)

在文档中找到以下内容: docs.python.org

  

来自文档:
  加入使用队列的进程

     

请记住,将项目放入队列的进程将在终止之前等待      直到所有缓冲的物品都被“进料器”螺纹送入底层管道。      (子进程可以调用队列的Queue.cancel_join_thread方法来避免此行为。)

     

这意味着无论何时使用队列,您都需要确保所有已放置的项目      在加入进程之前,最终将删除队列中的队列。      否则,您无法确定已将项目放入队列的进程将终止。      还要记住,非守护进程将自动加入。

当我解读时,一个进程,这里是p2 = jobHandler,不应该在将项放入队列后立即退出,以避免排队的数据松散。 无法找到对句子的任何解释否则你不能......已经将队列中的项目终止。

除此之外,我想评论你的代码。我意识到这个代码已经简化了。

  1. 避免将启动时执行的代码置于if __name__ == '__main__':

    之外
      

    来自文档:    安全导入主模块    应该使用if 名称 ==' main '来保护程序的“切入点”:

    job_queue = mp.Queue() error_queue = mp.Queue() for i in range(10): job_queue.put(i)

  2. def job_handler / error_handler

    中的
  3. .close()

    except: ... job_queue.close()

    这是错误的,因为job_handler进程永远不会在此队列中放置消息 这也适用于进程error_handler和error_queue.close()

  4.   

    来自文档:
         表示当前进程不再向此队列放置数据      一旦将所有缓冲数据刷新到管道,后台线程将退出      当队列被垃圾收集时,会自动调用此方法。

      def job_handler / error_handler中的
    1. .join_thread() 这是无用的,因为job_handler进程不会将消息放在此队列上。因此.join_thread 没有。 对于进程error_handler也是如此。

      except:
      ...
      job_queue.join_thread()

      # t2

      def error_handler(error_queue): ... error_queue.close() error_queue.join_thread()

    2. 使用Exit(1)代替return 1
      错误代码' 1' 无法通过p2.exitcode.进行捕获 将一个过程想象成一个自己的程序而不是一个函数。

      return 1

    3. 尝试以下方法:

      # t1
      error_queue.put('Error')
      error_queue.close()
      
      # Give the error_handler a chance to get a timeslice
      time.sleep(0.2)
      
      error_queue.join_thread()
      #job_queue.close()
      #job_queue.join_thread()
      # t2
      exit(1)
      

      使用Python测试:3.4.2和Python:2.7.9

答案 2 :(得分:0)

让方案重现#t1 #t2 之间的延迟。

#t1 ,队列已满。进程 p2必须等待,直到所有缓冲的项目由“feeder”线程提供给底层管道。

警告:如果p2无法将所有邮件放入队列,则会变为死锁

在p2终止时,error_handler仍在从队列中提供消息。

注意: 对于我的环境,因为 是操作系统依赖 我必须在队列中至少放置 3,500 项才能获得此行为

这是分析输出:

Starting  
Start Error Handler  
Start Job handler  
main blocked until p2 terminates  
job=0  job=1  job=2  job=3
except job_handler  
    # t1  
    error_queue.put('Error') * 3500  
        error_handler result[1]=Error  
    close error_queue  
        error_handler result[100]=Error  
    # t2 delayed 0:00:02.318323  
exit(1) job_handler  
p2 terminates with exitcode=1  
    job_queue has 5 outstanding jobs, empty=False  
    get outstanding job 5,6,7,8,9  
        error_handler result[1000]=Error  
        error_handler result[2000]=Error  
        error_handler result[3000]=Error  
exit error_handler got 3500 result=Error  
p1 terminates with exitcode=0  
    error_queue has 0 outstanding message(s), empty=True  
END __main__  

Process finished with exit code 0

答案 3 :(得分:0)

在加入使用队列的进程之前,您应该先清空队列。否则将导致死锁。

下面是从python multiprocessing doc复制过来的。

  

使用队列的加入过程

     

请记住,将项目放入队列的进程将在终止之前等待,直到所有缓冲的项目由“ feeder”线程馈送到基础管道为止。 (子进程可以调用队列的cancel_join_thread()方法来避免这种行为。)

     

这意味着,每当您使用队列时,都需要确保在加入该进程之前,将最终删除队列中已放置的所有项目。否则,您无法确定将项目放入队列的进程将终止。还请记住,非守护进程将自动加入。

     

将导致死锁的示例如下:

from multiprocessing import Process, Queue

def f(q):
   q.put('X' * 1000000)

if __name__ == '__main__':
   queue = Queue()
p = Process(target=f, args=(queue,))
p.start()
p.join()                    # this deadlocks
obj = queue.get()
     

这里的解决方法是交换最后两行(或简单地删除p.join()行)。