Python多进程环境下列表append操作无效问题排查
核心问题:Python多进程中全局列表追加操作失效
问题现象
在Python多进程场景下,向全局列表ok = []追加元素e时无任何效果,但ok.append(e)的上一行print(e)能正常输出e的有效值。核心问题为列表追加操作失效,无需关注业务逻辑。
尝试过的解决方案
- 将
ok = []放在if __name__=='__main__':代码块内,触发NameError: name 'ok' is not defined错误; - 在
some_function内使用global ok声明全局变量,问题依旧存在。
排查结果
在some_function和程序末尾打印id(ok)后发现,各进程的ok内存地址完全不同,最终主进程输出的ok列表为空。
相关代码
import time import multiprocessing as mp ratios1 = [1/x for x in range(1,11)] ratios2 = [y/1 for y in range(1,11)] x = 283 y = 436 ok = [] def some_function(x_, y_): list_ = [[a, b] for a in range(1, 1980 + 1) for b in range(1, 1980 + 1) if a / b == x_ / y_] for e in list_: if not e[0] in [h[0] for h in ok]: if not e[1] in [u[1] for u in ok]: print(e) ok.append(e) if __name__=='__main__': processes = [] if x / y in ratios1 or x / y in ratios2: some_function(x_=x, y_=y) else: for X_, Y_ in [ [x, y], [x - 1, y], [x, y - 1], [x + 1, y], [x, y + 1], [x - 2, y], [x, y - 2], [x + 2, y], [x, y + 2], [x - 3, y], [x, y - 3], [x + 3, y], [x, y + 3] ]: p = mp.Process(target=some_function, args=(X_,Y_)) processes.append(p) start = time.time() for p_ in processes: p_.start() for p_ in processes: p_.join() end = time.time() print(f"finished in {end - start} sec") print(ok)
运行输出
[...] # 其他"e"值 [283, 433] # 某个"e"值 [566, 866] # 某个"e"值 [849, 1299] # 某个"e"值 [1132, 1732] # 某个"e"值 finished in 0.8476874828338623 sec # 执行时间 [] # 最终输出的"ok"列表
追加id打印后的输出
2489040444480 3014871358528 2324227431488 2471301880896 1803966487616 2531583073344 1665411652672 2149818113088 2330038901824 1283883998272 2498472320064 2147028311104 2509405887552 finished in 0.8341867923736572 sec 2589544128640 []
解决方案
Python多进程模型中,每个子进程都会复制主进程的内存空间,你定义的全局列表ok在每个子进程里都是独立的副本,子进程对副本的修改不会同步到主进程或其他子进程。要解决这个问题,需要使用进程间共享的数据结构:
方案:使用multiprocessing.Manager创建共享列表
Manager可以创建跨进程共享的对象,修改后的内容会同步到所有进程。修改后的代码如下:
import time import multiprocessing as mp ratios1 = [1/x for x in range(1,11)] ratios2 = [y/1 for y in range(1,11)] x = 283 y = 436 def some_function(x_, y_, ok): list_ = [[a, b] for a in range(1, 1980 + 1) for b in range(1, 1980 + 1) if a / b == x_ / y_] for e in list_: # 加锁避免多进程并发修改导致数据不一致 with ok.get_lock(): if not e[0] in [h[0] for h in ok]: if not e[1] in [u[1] for u in ok]: print(e) ok.append(e) if __name__=='__main__': # 使用Manager创建共享列表 with mp.Manager() as manager: ok = manager.list() processes = [] if x / y in ratios1 or x / y in ratios2: some_function(x_=x, y_=y, ok=ok) else: for X_, Y_ in [ [x, y], [x - 1, y], [x, y - 1], [x + 1, y], [x, y + 1], [x - 2, y], [x, y - 2], [x + 2, y], [x, y + 2], [x - 3, y], [x, y - 3], [x + 3, y], [x, y + 3] ]: p = mp.Process(target=some_function, args=(X_, Y_, ok)) processes.append(p) start = time.time() for p_ in processes: p_.start() for p_ in processes: p_.join() end = time.time() print(f"finished in {end - start} sec") print(list(ok))
关键说明
- 用
mp.Manager().list()替代原有全局列表,创建跨进程共享的列表对象; - 将共享列表作为参数传递给子进程函数,避免全局变量的副本问题;
- 多进程并发修改共享列表时,必须用
ok.get_lock()加锁,防止数据竞争导致的异常; - 最终输出时将共享列表转为普通列表(
list(ok)),方便查看完整结果。
原方案无效原因
- 把
ok放在__main__块内:子进程无法直接访问主进程内存空间内的变量,触发NameError; - 使用
global ok:子进程的global指向的是自身内存空间内的ok副本,修改不会同步到主进程。
内容的提问来源于stack exchange,提问作者Neverland1337
相关产品推荐
相关产品推荐

