Python 3在队列为空之前调用join时,多处理队列死锁

时间:2015-07-27 23:59:51

标签: python multithreading

我有一个问题需要了解python 3中multiprocessing模块中的队列

这就是他们在programming guidelines中所说的:

请记住,将项目放入队列的进程将等待 终止,直到所有缓冲的项目由“馈线”线程馈送到 底层管道。 (子进程可以调用 Queue.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()line)。

显然,queue.get()之后不应调用join()

但是,有一些使用队列的示例get之后调用join

import multiprocessing as mp
import random
import string

# define a example function
def rand_string(length, output):
    """ Generates a random string of numbers, lower- and uppercase chars. """
    rand_str = ''.join(random.choice(
                string.ascii_lowercase
                + string.ascii_uppercase
                + string.digits)
    for i in range(length))
        output.put(rand_str)

 if __name__ == "__main__":
     # Define an output queue
     output = mp.Queue()

     # Setup a list of processes that we want to run
     processes = [mp.Process(target=rand_string, args=(5, output))
                    for x in range(2)]

     # Run processes
    for p in processes:
        p.start()

    # Exit the completed processes
    for p in processes:
        p.join()

    # Get process results from the output queue
    results = [output.get() for p in processes]

    print(results)

我已运行此程序并且可以正常运行(也作为StackOverFlow问题Python 3 - Multiprocessing - Queue.get() does not respond的解决方案发布)。

有人可以帮我理解僵局的规则是什么吗?

2 个答案:

答案 0 :(得分:31)

允许在进程之间传输数据的多处理中的队列实现依赖于标准的OS管道。

操作系统管道不是无限长,因此在put()操作期间可以在操作系统中阻止对数据进行排队的进程,直到某个其他进程使用get()从队列中检索数据。

对于少量数据,例如示例中的数据,主进程可join()生成所有子进程,然后获取数据。这通常很有效,但不能扩展,并且不清楚何时会破坏。

但它肯定会打破大量数据。子进程将在put()中被阻止,等待主进程使用get()从队列中删除一些数据,但主进程在join()中被阻止,等待子进程完成。这会导致死锁。

以下是用户this exact issue的示例。我在答案中发布了一些代码,帮助他解决了问题。

答案 1 :(得分:0)

在从共享队列中获取所有消息之前,不要在进程对象上调用join()

我使用以下解决方法允许进程在处理其所有结果之前退出:

results = []
while True:
    try:
        result = resultQueue.get(False, 0.01)
        results.append(result)
    except queue.Empty:
        pass
    allExited = True
    for t in processes:
        if t.exitcode is None:
            allExited = False
            break
    if allExited & resultQueue.empty():
        break

它可以缩短,但我把它留了更长时间以便新手更清楚。

此处resultQueue是与multiprocess.Queue个对象共享的multiprocess.Process。在这段代码之后,您将获得包含队列中所有消息的result数组。

问题是接收消息的队列管道的输入缓冲区可能会变满,导致写入程序无限阻塞,直到有足够的空间来接收下一条消息。所以你有三种方法可以避免阻塞:

  • 增加multiprocessing.connection.BUFFER尺寸(不太好)
  • 减少邮件大小或其数量(不太好)
  • 从队列中获取消息(好方法)