"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()" を使ってください
      。

   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()
