Python の Manager で共有オブジェクトを使う
Python の multiprocessing.Manager は、プロセス間で共有できるオブジェクトを提供するクラスです。Value や Array と違い、リストや辞書などの複雑なデータ構造も共有できます。
Manager の基本
Manager() は共有オブジェクトを管理するサーバープロセスを起動します。
from multiprocessing import Process, Manager
def worker(shared_list):
shared_list.append("Hello from child")
if __name__ == "__main__":
with Manager() as manager:
shared_list = manager.list()
p = Process(target=worker, args=(shared_list,))
p.start()
p.join()
print(shared_list) # ['Hello from child']
manager.list() で共有リストを作成し、子プロセスから変更を加えられます。
Manager が提供する共有オブジェクト
Manager は様々な共有データ構造を提供します。
| list() | 共有リスト |
| dict() | 共有辞書 |
| Value(typecode, value) | 共有値 |
| Array(typecode, sequence) | 共有配列 |
| Namespace() | 属性を持つオブジェクト |
| Queue() | 共有キュー |
| Lock() | 共有ロック |
| Event() | 共有イベント |
共有辞書を使う
manager.dict() で共有辞書を作成できます。
from multiprocessing import Process, Manager
def update_dict(d, key, value):
d[key] = value
if __name__ == "__main__":
with Manager() as manager:
shared_dict = manager.dict()
processes = []
for i in range(3):
p = Process(target=update_dict, args=(shared_dict, f"key{i}", i))
processes.append(p)
p.start()
for p in processes:
p.join()
print(dict(shared_dict)) # {'key0': 0, 'key1': 1, 'key2': 2}
複数のプロセスから同時に辞書を更新できます。
Namespace を使う
Namespace は属性を持つシンプルなオブジェクトです。
from multiprocessing import Process, Manager
def update_namespace(ns):
ns.count += 1
ns.name = "Updated"
if __name__ == "__main__":
with Manager() as manager:
ns = manager.Namespace()
ns.count = 0
ns.name = "Initial"
p = Process(target=update_namespace, args=(ns,))
p.start()
p.join()
print(ns.count) # 1
print(ns.name) # Updated
設定値の共有などに便利です。
Manager と Value/Array の違い
Value/Array
共有メモリを直接使う。高速だが、ctypes の型に限定される。
Manager
サーバープロセス経由でアクセス。柔軟だが、オーバーヘッドが大きい。
パフォーマンスが重要な場合は Value や Array を、柔軟性が必要な場合は Manager を使います。
ネストしたオブジェクトの注意点
Manager のリストや辞書で、ネストしたオブジェクトを変更する場合は注意が必要です。
from multiprocessing import Manager
with Manager() as manager:
shared_list = manager.list([[1, 2], [3, 4]])
# これは反映されない
shared_list[0].append(5)
print(shared_list[0]) # [1, 2] のまま
# 正しい方法
temp = shared_list[0]
temp.append(5)
shared_list[0] = temp
print(shared_list[0]) # [1, 2, 5]
ネストしたオブジェクトへの変更は、明示的に再代入しないと反映されません。
SyncManager のカスタマイズ
Manager を継承して、カスタムの共有オブジェクトを作ることもできます。
from multiprocessing.managers import BaseManager
class Counter:
def __init__(self):
self._value = 0
def increment(self):
self._value += 1
return self._value
def value(self):
return self._value
class MyManager(BaseManager):
pass
MyManager.register('Counter', Counter)
if __name__ == "__main__":
with MyManager() as manager:
counter = manager.Counter()
print(counter.increment()) # 1
print(counter.increment()) # 2
独自のクラスを register() で登録して、プロセス間で共有できます。