1.进程join造成死锁
当代码如下,进程正常结束:
from multiprocessing import Process, Queue
def f(q):
q.put('X' * 60000)
if __name__ == '__main__':
queue = Queue()
p1 = Process(target=f, args=(queue,))
p1.start()
p1.join() # this deadlocks
obj = queue.get()
但将f函数中的60000改成70000时,程序无法终止:
from multiprocessing import Process, Queue
def f(q):
q.put('X' * 70000)
if __name__ == '__main__':
queue = Queue()
p1 = Process(target=f, args=(queue,))
p1.start()
p1.join() # this deadlocks
obj = queue.get()

根据官方文档的说明,当一个进程被join时,Python会检查被放入Queue中的数据是否已经全部删除,若没有删除,则进程会一直处于等待状态。在第一份代码中,程序虽然被join了,但是有可能当Queue的size小于某个阈值时,进程join的时候会将其视为空Queue,所以可以正常结束。
2.Queue.empty()不可信
当代码如下,有时程序可以结束,而有时不可以。
from multiprocessing import Process, Queue
import os
def proc_read(q):
print('Process(%s) is reading...' % os.getpid())
cnt = 0
while True:
cnt += 1
url = q.get(True)
print("### final: {}".format(cnt))
if __name__=='__main__':
q = Queue()
for i in range(10000):
q.put(1)
proc_reader = Process(target=proc_read, args=(q,))
proc_reader.start()
while not q.empty() :
pass
print("The size of Queue is {}".format(q.qsize()))
# proc_reader进程里是死循环,无法等待其结束,只能强行终止:
proc_reader.terminate()
print("The process can't finish.")
结果:

原因是Queue.empty()是不可信的,可能会返回错误的结果。如果在生产者-消费者问题中要用到Queue,让消费者知道什么时候队列为空有两种方式:
- 使用哨兵,当生产者进程put完之后,再向队列中put一个None,当消费者进程读到None之后,则知道自己的工作已经完成了(如果是多生产者和多消费者,每个生产者结束都应该put一个None,消费者检测到和生产者个数相同的None即可得知任务结束,同时消费者get到一个None之后应该把它再放回队列)。
- 使用先验知识,先计算出生产者总共会生产多少数据,然后消费者处理完对应数量的数据即可得知任务结束。
不过仍然不清楚为什么这个case中,empty会好端端地在队列中有数据的情况下返回了一个True,以及整个进程为什么不能终止,以后有时间读源码再仔细了解一下这个机制。
参考资料:
https://docs.python.org/zh-cn/3.6/library/multiprocessing.html
https://stackoverflow.com/questions/32395150/how-to-make-pythons-multiprocessing-queues-empty-method-return-the-correct?noredirect=1&lq=1
本文探讨了Python多进程环境下Queue的使用误区,包括进程join导致的死锁现象及Queue.empty()方法的不可靠性。阐述了如何通过哨兵值或先验知识判断生产者-消费者模型中队列是否真正为空,避免程序异常终止。
711

被折叠的 条评论
为什么被折叠?



