在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()
这基本上有效,但在我更复杂的程序中,两个评论点t1
和t2
之间存在很长的时间差异(约5分钟)。所以我有两个问题:
close()
和join_thread()
,以表明它已经完成了?我认为,当我结束它们时,子进程会隐式地执行此操作,例如通过按照所述here返回:join_thread()加入后台主题。这只能在以后使用 已经调用了close()。它会阻塞,直到后台线程退出, 确保缓冲区中的所有数据都已刷新到管道。
默认情况下,如果进程不是队列的创建者,则退出 它将尝试加入队列的后台线程。这个过程可以 调用cancel_join_thread()使join_thread()不执行任何操作。
答案 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,不应该在将项放入队列后立即退出,以避免排队的数据松散。 无法找到对句子的任何解释否则你不能......已经将队列中的项目终止。
除此之外,我想评论你的代码。我意识到这个代码已经简化了。
避免将启动时执行的代码置于if __name__ == '__main__':
来自文档: 安全导入主模块 应该使用if 名称 ==' main '来保护程序的“切入点”:
job_queue = mp.Queue()
error_queue = mp.Queue()
for i in range(10):
job_queue.put(i)
.close()
except:
...
job_queue.close()
这是错误的,因为job_handler进程永远不会在此队列中放置消息 这也适用于进程error_handler和error_queue.close()
来自文档:
表示当前进程不再向此队列放置数据 一旦将所有缓冲数据刷新到管道,后台线程将退出 当队列被垃圾收集时,会自动调用此方法。
.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()
使用Exit(1)
代替return 1
错误代码' 1' 无法通过p2.exitcode.
进行捕获
将一个过程想象成一个自己的程序而不是一个函数。
return 1
尝试以下方法:
# 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()行)。