流程完成但无法加入吗?

时间:2019-07-05 10:08:25

标签: python-3.x queue python-multiprocessing

为了加速某些任务,我将Process子类化,以创建一个工作器,该工作器将处理样本中的数据。一些管理类将提供数据并读取输出(使用两个Queue实例)。对于异步操作,我使用put_nowaitget_nowait。最后,我要向进程发送一个特殊的退出代码,此代码将中断其内部循环。但是...永远不会发生。这是一个最小的可重现示例:

import multiprocessing as mp

class Worker(mp.Process):
  def __init__(self, in_queue, out_queue):
    super(Worker, self).__init__()
    self.input_queue = in_queue
    self.output_queue = out_queue

  def run(self):
    while True:
      received = self.input_queue.get(block=True)
      if received is None:
        break
      self.output_queue.put_nowait(received)
    print("\tWORKER DEAD")


class Processor():
  def __init__(self):
    # prepare
    in_queue = mp.Queue()
    out_queue = mp.Queue()
    worker = Worker(in_queue, out_queue)
    # get to work
    worker.start()
    in_queue.put_nowait(list(range(10**5))) # XXX
    # clean up
    print("NOTIFYING")
    in_queue.put_nowait(None)
    #out_queue.get() # XXX
    print("JOINING")
    worker.join()

Processor()

此代码永远不会完成,像这样永久挂起:

NOTIFYING
JOINING
    WORKER DEAD

为什么?

我用XXX标记了两行。在第一个中,如果我发送的数据较少(例如10**4),那么一切将正常完成(进程按预期方式加入)。同样,在第二步中,如果我在通知工人完成任务后get()。我知道我遗漏了一些东西,但documentation中似乎没有任何内容。

1 个答案:

答案 0 :(得分:0)

文档中提到

  

将对象放入队列时,将对对象进行酸洗,然后后台线程将酸洗的数据刷新到基础管道。这会带来一些后果。[...]将对象放在空队列上之后,在队列的empty()方法返回False且get_nowait()可以在不引发队列的情况下返回。Empty。

https://docs.python.org/3.7/library/multiprocessing.html#pipes-and-queues

另外

  

无论何时使用队列,都需要确保已加入队列的所有项目最终都将在加入流程之前被删除。否则,您无法确定将项目放入队列的进程将终止。

https://docs.python.org/3.7/library/multiprocessing.html#multiprocessing-programming

这意味着您描述的行为可能是由于工作人员self.output_queue.put_nowait(received)之间的竞争状况和工作人员worker.join()中的__init__与该工作人员加入竞赛状态引起的。如果加入的速度快于将其加入队列的速度,那么一切都将顺利完成。如果太慢,则队列中有一个项目,工作人员将无法加入。

在主进程中取消对out_queue.get()的注释将清空队列,从而允许加入。但是,如果队列已经空了,返回队列很重要,因此使用超时可能是尝试等待竞速条件的一种选择,例如out_qeue.get(timeout=10)

可能重要的是保护主例程,尤其是对于Windows(python multiprocessing on windows, if __name__ == "__main__"