Python の Manager で共有オブジェクトを使う

Python の multiprocessing.Manager は、プロセス間で共有できるオブジェクトを提供するクラスです。ValueArray と違い、リストや辞書などの複雑なデータ構造も共有できます。

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

サーバープロセス経由でアクセス。柔軟だが、オーバーヘッドが大きい。

パフォーマンスが重要な場合は ValueArray を、柔軟性が必要な場合は 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() で登録して、プロセス間で共有できます。