multiprocessing.shared_memory --- プロセス間で直接アクセス可能な共有メモリ

ソースコード Lib/multiprocessing/shared_memory.py

Added in version 3.8.


このモジュールは、マルチコアもしくは対称型マルチプロセッサ(SMP) を持つマシン上で実行される、ひとつ以上のプロセスから参照される共有メモリの確保、管理を行うための SharedMemory クラスを提供します。共有メモリのライフサイクル管理、とりわけ複数のプロセスにわたる場合のために、 multiprocessing.managers モジュールでは BaseManager のサブクラス SharedMemoryManager も提供されます。

このモジュールでの共有メモリは「POSIX 形式」の共有メモリブロックを指し (実装が必ずしも明示的にそうなっているとは限りません)、「分散共有メモリ」ではありません。 この形式の共有メモリを使うと、異なるプロセスが揮発性メモリの共通 (共有) 領域を読み書きできます。 通常、プロセスがアクセスできるのは自分のメモリ空間だけですが、共有メモリではプロセス間でデータを共有でき、そのデータを含むメッセージを送り合う必要がありません。 ディスク、ソケット、その他の通信による共有ではデータのシリアライズ、復元、コピーが必要ですが、メモリを介して直接共有すると性能が大きく向上する場合があります。

class multiprocessing.shared_memory.SharedMemory(name=None, create=False, size=0, *, track=True)

新しい共有メモリブロックを作成するか、既存の共有メモリブロックに接続するための SharedMemory インスタンスを作成します。 各共有メモリブロックには一意の名前が割り当てられます。 そのため、あるプロセスが特定の名前で共有メモリブロックを作成し、別のプロセスが同じ名前を使ってそのブロックに接続できます。

プロセス間でデータを共有するリソースとして、共有メモリブロックは、それを作成したプロセスが終了しても存続する場合があります。 他のプロセスがまだ使う可能性のある共有メモリブロックに、あるプロセスがアクセスする必要がなくなったら、close() を呼び出してください。 どのプロセスからも必要なくなったら、適切に後始末するために unlink() を呼び出してください。

パラメータ:
  • name (str | None) -- 要求する共有メモリの一意の名前を文字列で指定します。 新しい共有メモリブロックを作成する際、名前に None (デフォルト) を指定すると、新しい名前を生成します。

  • create (bool) -- 新しい共有メモリブロックを作成する (True) か、既存のブロックに接続する (False) かを指定します。

  • size (int) -- 新しい共有メモリブロックを作成する際に要求するバイト数です。 一部のプラットフォームでは、そのメモリページサイズに基づく単位でメモリを割り当てるため、実際の共有メモリブロックのサイズは要求したサイズ以上になる場合があります。 既存のブロックに接続する場合、size 引数は無視します。

  • track (bool) -- True の場合、OS が自動で追跡しないプラットフォームでは、共有メモリブロックをリソーストラッカープロセスに登録します。 リソーストラッカーは、そのメモリにアクセスする他のプロセスがすべて後始末せずに終了しても、共有メモリを適切に後始末します。 multiprocessing の機能を使って共通の祖先から作成された Python プロセスは、1 つのリソーストラッカーを共有し、共有メモリセグメントの寿命を自動で管理します。 他の方法で作成された Python プロセスは、track を有効にして共有メモリにアクセスすると、それぞれ独自のリソーストラッカーを持ちます。 その結果、最初に終了したプロセスのリソーストラッカーが共有メモリを削除します。 この問題を避けるため、subprocess や独立した Python プロセスを使う場合、すでに管理を行う別のプロセスがあれば track を False に設定してください。 Windows は独自に追跡を行い、すべてのハンドルが閉じられると共有メモリを自動で削除するため、track を無視します。

バージョン 3.13 で変更: track 引数を追加しました。

close()

このインスタンスが持つ、共有メモリのファイル記述子またはハンドルを閉じます。 このインスタンスから共有メモリブロックにアクセスする必要がなくなったら、close() を呼び出してください。 すべてのハンドルを閉じても、実際のメモリが解放されるかどうかは OS によって異なります。 確実に後始末するには unlink() を使ってください。

実際の共有メモリブロックを削除します。 他のプロセスのものも含め、ハンドルの数にかかわらず、共有メモリブロックごとに 1 回だけ呼び出してください。 unlink() と close() はどちらの順序でも呼び出せますが、unlink() の後でブロック内のデータにアクセスしようとすると、プラットフォームによってはメモリアクセスエラーになる可能性があります。

Windows ではこのメソッドは何もしません。 共有メモリブロックを削除する唯一の方法は、すべてのハンドルを閉じることです。

buf

共有メモリブロックの内容を表す memoryview。

name

共有メモリブロックの一意の名前を読み取り専用で取得します。

size

共有メモリブロックのバイト単位のサイズを読み取り専用で取得します。

以下の例は、SharedMemory インスタンスの低水準の使い方を示します。

>>> from multiprocessing import shared_memory
>>> shm_a = shared_memory.SharedMemory(create=True, size=10)
>>> type(shm_a.buf)
<class 'memoryview'>
>>> buffer = shm_a.buf
>>> len(buffer)
10
>>> buffer[:4] = bytearray([22, 33, 44, 55])  # 複数のバイトを一度に変更
>>> buffer[4] = 100                           # 1 バイトずつ変更
>>> # 既存の共有メモリブロックに接続
>>> shm_b = shared_memory.SharedMemory(shm_a.name)
>>> import array
>>> array.array('b', shm_b.buf[:5])  # 新しい array.array にデータをコピー
array('b', [22, 33, 44, 55, 100])
>>> shm_b.buf[:5] = b'howdy'  # bytes を使って shm_b 経由で変更
>>> bytes(shm_a.buf[:5])      # shm_a 経由でアクセス
b'howdy'
>>> shm_b.close()   # 各 SharedMemory インスタンスを閉じる
>>> shm_a.close()
>>> shm_a.unlink()  # 共有メモリを解放するため、unlink を 1 回だけ呼び出す

以下の例は、NumPy 配列 と SharedMemory クラスを組み合わせ、2 つの異なる Python シェルから同じ numpy.ndarray にアクセスする実用的な使い方を示します。

>>> # 最初の Python 対話シェルで
>>> import numpy as np
>>> a = np.array([1, 1, 2, 3, 5, 8])  # 既存の NumPy 配列から始める
>>> from multiprocessing import shared_memory
>>> shm = shared_memory.SharedMemory(create=True, size=a.nbytes)
>>> # 共有メモリを使う NumPy 配列を作成
>>> b = np.ndarray(a.shape, dtype=a.dtype, buffer=shm.buf)
>>> b[:] = a[:]  # 元のデータを共有メモリにコピー
>>> b
array([1, 1, 2, 3, 5, 8])
>>> type(b)
<class 'numpy.ndarray'>
>>> type(a)
<class 'numpy.ndarray'>
>>> shm.name  # 名前を指定しなかったので自動で選ばれた
'psm_21467_46075'

>>> # 同じシェル、または同じマシン上の新しい Python シェルで
>>> import numpy as np
>>> from multiprocessing import shared_memory
>>> # 既存の共有メモリブロックに接続
>>> existing_shm = shared_memory.SharedMemory(name='psm_21467_46075')
>>> # この例では a.shape は (6,)、a.dtype は np.int64
>>> c = np.ndarray((6,), dtype=np.int64, buffer=existing_shm.buf)
>>> c
array([1, 1, 2, 3, 5, 8])
>>> c[-1] = 888
>>> c
array([  1,   1,   2,   3,   5, 888])

>>> # 最初の Python 対話シェルに戻ると、b にこの変更が反映されている
>>> b
array([  1,   1,   2,   3,   5, 888])

>>> # 2 番目の Python シェルで後始末
>>> del c  # 必須ではないが、配列をもう使わないことを示す
>>> existing_shm.close()

>>> # 最初の Python シェルで後始末
>>> del b  # 必須ではないが、配列をもう使わないことを示す
>>> shm.close()
>>> shm.unlink()  # 最後に共有メモリブロックを解放する
class multiprocessing.managers.SharedMemoryManager([address[, authkey]])

プロセス間で共有メモリブロックを管理するための multiprocessing.managers.BaseManager のサブクラスです。

SharedMemoryManager インスタンスの start() を呼び出すと、新しいプロセスが開始されます。 このプロセスは、それを通じて作成したすべての共有メモリブロックのライフサイクルを管理するためだけに動作します。 管理する共有メモリブロックをすべて解放するには、インスタンスの shutdown() を呼び出します。 すると、そのプロセスが管理するすべての SharedMemory オブジェクトで unlink() を呼び出し、プロセス自体を停止します。 SharedMemoryManager を通じて SharedMemory インスタンスを作ると、共有メモリリソースを手動で追跡して解放する必要がなくなります。

このクラスは、SharedMemory インスタンスを作成して返すメソッドと、共有メモリを使うリスト類似オブジェクト (ShareableList) を作成するメソッドを提供します。

継承した省略可能な引数 address と authkey、および別のプロセスから既存の SharedMemoryManager サービスに接続する際の使い方については、BaseManager を参照してください。

SharedMemory(size)

指定された size バイトの新しい SharedMemory オブジェクトを作成して返します。

ShareableList(sequence)

入力された sequence の値で初期化した新しい ShareableList オブジェクトを作成して返します。

以下の例は、SharedMemoryManager の基本的な仕組みを示します。

>>> from multiprocessing.managers import SharedMemoryManager
>>> smm = SharedMemoryManager()
>>> smm.start()  # 共有メモリブロックを管理するプロセスを開始
>>> sl = smm.ShareableList(range(4))
>>> sl
ShareableList([0, 1, 2, 3], name='psm_6572_7512')
>>> raw_shm = smm.SharedMemory(size=128)
>>> another_sl = smm.ShareableList('alpha')
>>> another_sl
ShareableList(['a', 'l', 'p', 'h', 'a'], name='psm_6572_12221')
>>> smm.shutdown()  # sl、raw_shm、another_sl の unlink() を呼び出す

以下の例は、with 文で SharedMemoryManager オブジェクトを使い、不要になった共有メモリブロックをすべて確実に解放する方法を示します。 この使い方の方が便利な場合があります。

>>> with SharedMemoryManager() as smm:
...     sl = smm.ShareableList(range(2000))
...     # 処理を 2 つのプロセスに分け、部分的な結果を sl に格納
...     p1 = Process(target=do_work, args=(sl, 0, 1000))
...     p2 = Process(target=do_work, args=(sl, 1000, 2000))
...     p1.start()
...     p2.start()  # multiprocessing.Pool の方が効率的かもしれません
...     p1.join()
...     p2.join()   # 両プロセスのすべての処理が終わるまで待つ
...     total_result = sum(sl)  # sl に格納された部分的な結果を集計

with 文で SharedMemoryManager を使うと、そのマネージャーで作成した共有メモリブロックは、with 文のコードブロックの実行が終わるとすべて解放されます。

class multiprocessing.shared_memory.ShareableList(sequence=None, *, name=None)

格納するすべての値を共有メモリブロックに置く、ミュータブルなリスト類似オブジェクトを提供します。 そのため、格納できる値は以下の組み込みデータ型に制限されます。

  • int (符号付き 64 ビット)

  • float

  • bool

  • str (UTF-8 でエンコードしたときに各値が 10M バイト未満)

  • bytes (各値が 10M バイト未満)

  • None

また、全体の長さを変更できず (append()、insert() などはありません)、スライスによる新しい ShareableList インスタンスの動的な作成をサポートしない点も、組み込みの list 型とは大きく異なります。

sequence は、新しい ShareableList に値を設定するために使います。 代わりに、一意の共有メモリ名で既存の ShareableList に接続する場合は、None に設定してください。

name は、SharedMemory の説明にある、要求する共有メモリの一意の名前です。 既存の ShareableList に接続する場合は、sequence を None のままにして、その共有メモリブロックの一意の名前を指定してください。

注釈

bytes と str の値には既知の問題があります。 値が \x00 のヌルバイトまたはヌル文字で終わる場合、ShareableList からインデックスで取得すると、それらが 通知なく除去される 場合があります。 この .rstrip(b'\x00') の動作はバグとみなされており、将来なくなる可能性があります。 gh-106939 を参照してください。

末尾のヌルの除去が問題になるアプリケーションでは、格納時に必ず値の末尾へ 0 以外のバイトを追加し、取得時に必ずそれを取り除くことで回避してください。

>>> from multiprocessing import shared_memory
>>> nul_bug_demo = shared_memory.ShareableList(['?\x00', b'\x03\x02\x01\x00\x00\x00'])
>>> nul_bug_demo[0]
'?'
>>> nul_bug_demo[1]
b'\x03\x02\x01'
>>> nul_bug_demo.shm.unlink()
>>> padded = shared_memory.ShareableList(['?\x00\x07', b'\x03\x02\x01\x00\x00\x00\x07'])
>>> padded[0][:-1]
'?\x00'
>>> padded[1][:-1]
b'\x03\x02\x01\x00\x00\x00'
>>> padded.shm.unlink()
count(value)

value の出現回数を返します。

index(value)

value が最初に現れるインデックスを返します。 value が存在しない場合は ValueError を送出します。

format

現在格納されているすべての値に使う struct のパック形式を保持する読み取り専用属性。

shm

値が格納されている SharedMemory インスタンス。

以下に ShareableList インスタンスの利用例を示します。

>>> from multiprocessing import shared_memory
>>> a = shared_memory.ShareableList(['howdy', b'HoWdY', -273.154, 100, None, True, 42])
>>> [ type(entry) for entry in a ]
[<class 'str'>, <class 'bytes'>, <class 'float'>, <class 'int'>, <class 'NoneType'>, <class 'bool'>, <class 'int'>]
>>> a[2]
-273.154
>>> a[2] = -78.5
>>> a[2]
-78.5
>>> a[2] = 'dry ice'  # Changing data types is supported as well
>>> a[2]
'dry ice'
>>> a[2] = 'larger than previously allocated storage space'
Traceback (most recent call last):
  ...
ValueError: exceeds available storage for existing str
>>> a[2]
'dry ice'
>>> len(a)
7
>>> a.index(42)
6
>>> a.count(b'howdy')
0
>>> a.count(b'HoWdY')
1
>>> a.shm.close()
>>> a.shm.unlink()
>>> del a  # Use of a ShareableList after call to unlink() is unsupported

以下の例は、共有メモリブロックの名前を指定することで、1 つ、2 つ、あるいは多数のプロセスが同じ ShareableList にアクセスできることを示します。

>>> b = shared_memory.ShareableList(range(5))         # In a first process
>>> c = shared_memory.ShareableList(name=b.shm.name)  # In a second process
>>> c
ShareableList([0, 1, 2, 3, 4], name='...')
>>> c[-1] = -999
>>> b[-1]
-999
>>> b.shm.close()
>>> c.shm.close()
>>> c.shm.unlink()

以下の例は、必要に応じて ShareableList (および内部の SharedMemory) オブジェクトを pickle 化し、復元できることを示します。 ただし、共有する対象は同じオブジェクトのままです。 復元されたオブジェクトは同じ一意の名前を持ち、同じ名前の既存のオブジェクトに接続するだけだからです (そのオブジェクトがまだ存在する場合)。

>>> import pickle
>>> from multiprocessing import shared_memory
>>> sl = shared_memory.ShareableList(range(10))
>>> list(sl)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> deserialized_sl = pickle.loads(pickle.dumps(sl))
>>> list(deserialized_sl)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> sl[0] = -1
>>> deserialized_sl[1] = -2
>>> list(sl)
[-1, -2, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(deserialized_sl)
[-1, -2, 2, 3, 4, 5, 6, 7, 8, 9]
>>> sl.shm.close()
>>> sl.shm.unlink()