Python multiprocessing注册多Queue返回同一队列问题排查
问题描述
参考以下多进程Manager注册队列的实现代码:
from multiprocessing import Queue from multiprocessing.managers import SyncManager def make_manager(q_names, port): qs = [Queue() for _ in q_names] class MyManager(SyncManager): pass for q_name, q in zip(q_names, qs): MyManager.register(q_name, callable=lambda: q) auth = b'myauth' try: manager = MyManager(address=('', port), authkey=auth) manager.start() except: print('Could not start manager') return manager manager = make_manager(['aa', 'bb'], 8000) print(manager.aa()) print(manager.bb())
代码运行输出如下,两个注册方法返回的队列内存地址完全一致,是同一个对象:
<multiprocessing.queues.Queue object at 0x7f7ed1c83eb0> <multiprocessing.queues.Queue object at 0x7f7ed1c83eb0>
根因分析
问题和multiprocessing模块本身无关,出在Python lambda的闭包延迟绑定特性上:
- 循环中定义的lambda不会在定义时立即捕获当前轮次的变量值,只会保留对变量本身的引用
- 当后续实际调用lambda时,才会去查找对应变量的当前值,此时循环已经执行完毕,变量
q指向的是循环最后一轮的队列对象,因此所有注册的方法最终都会返回同一个队列。
可以用极简示例复现这个特性:
ll = [] for i in range(2): ll.append(lambda: i) for l in ll: print(l()) # 两次输出均为1
修复方案
利用Python函数默认参数在定义时求值绑定的特性,将循环变量作为lambda的默认参数传入,即可固定每一轮lambda对应的变量值:
将注册逻辑中的MyManager.register(q_name, callable=lambda: q)
修改为MyManager.register(q_name, callable=lambda q=q: q)
修改后两个注册方法会分别返回初始化时创建的两个独立队列对象,符合预期。
内容的提问来源于stack exchange,提问作者AlwaysLearning
相关产品推荐
相关产品推荐

