Python多重处理过程不会终止

时间:2019-03-09 18:19:39

标签: python multiprocessing

当我尝试使用多处理库在python中实现并行操作时,我发现某些进程不会以非直观的方式终止。

我的程序包括:

  • 一个队列,用于进程之间的数据传输
  • 用户进程,该进程使用通过队列接收的数据来计算内容
  • 两个制造者流程,它们生成数据并将其推送到队列中

下面是一个简化的示例。 make_data生成随机数并推送到队列,use_data接收数据并计算平均值。总共生成2 * 1000 = 2000个数字,并全部使用。此代码按预期运行。毕竟,所有进程都终止了,队列中没有数据。

import random
from multiprocessing import Process, Queue

q = Queue(maxsize=10000)
def make_data(q):
    for i in range(1000):
        x = random.random()
        q.put(x)
    print("final line of make data")

def use_data(q):
    i = 0
    res = 0.0
    while i < 2000:
        if q.empty():
            continue
        i += 1
        x = q.get()
        res = res*(i-1)/i + x/i
    print("iter %6d, avg = %.5f" % (i, res))

u = Process(target=use_data, args=(q,))
u.start()

p1 = Process(target=make_data, args=(q,))
p1.start()
p2 = Process(target=make_data, args=(q,))
p2.start()


u.join(timeout=10)
p1.join(timeout=10)
p2.join(timeout=10)
print(u.is_alive(), p1.is_alive(), p2.is_alive(), q.qsize())

结果:

final line of make data
final line of make data
iter   2000, avg = 0.49655
False False False 0

当我让制造商生成超出必要数据的数据时,事情发生了变化。 下面的代码与上面的代码的不同之处仅在于每个制造商生成5000个数据,因此并非所有数据都被使用。运行此命令时,它会打印最后一行的消息,但是制造商进程永远不会终止(需要Ctrl-C停止)。

import random
from multiprocessing import Process, Queue

q = Queue(maxsize=10000)
def make_data(q):
    for i in range(5000):
        x = random.random()
        q.put(x)
    print("final line of make data")

def use_data(q):
    i = 0
    res = 0.0
    while i < 2000:
        if q.empty():
            continue
        i += 1
        x = q.get()
        res = res*(i-1)/i + x/i
    print("iter %6d, avg = %.5f" % (i, res))

u = Process(target=use_data, args=(q,))
u.start()

p1 = Process(target=make_data, args=(q,))
p1.start()
p2 = Process(target=make_data, args=(q,))
p2.start()


u.join(timeout=10)
p1.join(timeout=10)
p2.join(timeout=10)
print(u.is_alive(), p1.is_alive(), p2.is_alive(), q.qsize())

结果:

final line of make data
final line of make data
iter   2000, avg = 0.49388
False True True 8000
# and never finish

在我看来,所有进程都运行到最后,所以想知道为什么它们仍然存在。有人可以帮助我了解这种现象吗?

我通过miniconda发行版在python 3.6.6上运行了该程序。

1 个答案:

答案 0 :(得分:2)

将项目放入队列的子进程被卡住,试图将对象实际放入队列。

正常的,非多重处理的Queue对象完全在单个进程的地址空间中实现。在这种情况下,maxsizeput()调用块之前可以入队的项目数。但是,多处理Queue对象是使用IPC机制实现的;通常是管道。并且OS管道可以将有限数量的字节排队(通常限制为8KB)。因此,当您的use_data()进程在仅使2000个项目出队后终止时,make_data()进程就会阻塞,因为在退出时将本地排队的项目刷新到IPC中时,它们的IPC通道已满。这意味着它们实际上并没有退出,因此join()这些进程的尝试会无限期地阻塞。

实际上,您已经创建了一个死锁。发生的确切阈值取决于IPC通道可以缓冲多少数据。例如,在我的一台Linux服务器上,您的第二个示例在u.join()p1.join()之间插入了该示例,可以可靠地工作:

for _ in range(4000):
    q.get()

稍微减小该范围(例如降至3990)会产生间歇性的挂起。将该范围进一步减小(例如,减小到3500)将始终挂起,因为至少有一个进程将数据填充到队列块中,同时将其项刷新到IPC通道中。

这个故事的教训?在尝试等待进程终止之前,请始终完全耗尽多处理队列。