Python multiprocessing 模組中,進程間記憶體隔離導致全域變數無法直接共享。本文透過實驗驗證問題本質,系統介紹 Manager、Value/Array 兩大共享機制,並深入探討資料同步與鎖機制。
from multiprocessing import Process
import os
vip_list = []
def testFunc(cc):
vip_list.append(cc)
print 'process id:', os.getpid()
if __name__ == '__main__':
threads = []
for ll in range(10):
t = Process(target=testFunc, args=(ll,))
t.daemon = True
threads.append(t)
for t in threads: t.start()
for t in threads: t.join()
print vip_list # 輸出:[]
原因:每個進程擁有獨立記憶體空間,子進程修改的是複製品,父進程原列表不變。
from multiprocessing import Process, Manager
manager = Manager()
vip_list = manager.list() # 共享列表代理
# ... 其餘相同 ...
# 輸出:[3, 2, 1, 7, 5, 0, 6, 8, 4, 9](順序不定)
Manager 啟動一個伺服器進程持有 Python 物件,其他進程透過代理操作,支援 list, dict, Namespace, Lock, Queue 等類型。
from multiprocessing import Process, Value, Array
def f(n, a):
n.value = 3.1415927
for i in range(len(a)):
a[i] = -a[i]
if __name__ == '__main__':
num = Value('d', 0.0) # 雙精度浮點數
arr = Array('i', range(10)) # 有號整數陣列
p = Process(target=f, args=(num, arr))
p.start(); p.join()
print num.value # 3.1415927
print arr[:] # [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
型別代碼:'d'(double), 'i'(int), 'f'(float), 'c'(char) 等。
即使共享變數,並發修改仍會導致資料不一致:
from multiprocessing import Process, Manager
manager = Manager()
sum = manager.Value('i', 0)
def testFunc(cc):
sum.value += cc # 非原子操作:讀取-修改-寫入
# 100 個進程各加 1,預期 100,實際常 < 100
from multiprocessing import Process, Manager, Lock
lock = Lock()
manager = Manager()
sum = manager.Value('i', 0)
def testFunc(cc, lock):
with lock:
sum.value += cc
# ... 建立進程時傳入 lock ...
# 輸出穩定為 100
「通常在進程間共享資料並非最佳選擇,因同步議題複雜;採用 Actor 模型透過訊息傳遞溝通通常更佳。盡可能避免共享狀態。」 —— Python 官方文檔
multiprocessing.Queue(內建鎖、線程安全)multiprocessing.Pool.map() 自動分發收集fork 時複製(Copy-on-Write),無需額外機制