スレッドセーフ性の保証
**********************

このページでは、Python のフリースレッドビルドにおける組み込み型のスレ
ッドセーフ性の保証を説明します。 ここで説明する保証は、*GIL* を無効に
して Python を使う場合 (フリースレッドモード) に適用されます。 GIL が
有効な場合、ほとんどの操作は暗黙に直列化されます。

フリースレッドの Python でスレッドセーフなコードを書くための一般的な指
針は、Python のフリースレッド対応 を参照してください。


スレッドセーフ性のレベル
========================

C API のドキュメントでは、各関数のスレッドセーフ性の保証を次のレベルで
表します。 安全性の低いものから高いものの順に示します。


並行使用不可 (Incompatible)
---------------------------

外部で同期しても、安全に並行使用できない関数や操作です。 このレベルの
コードは通常、同期せずにグローバル状態へアクセスするため、プログラムの
存続期間を通して、単一のスレッドからだけ呼び出さなければなりません。

例: シグナルハンドラや環境変数など、プロセス全体の状態を変更する関数で
す。 外部でロックを取得していても、複数のスレッドからの並行呼び出しが
、ランタイムやほかのライブラリと競合する可能性があります。


外部同期により並行使用可能 (Compatible)
---------------------------------------

呼び出し側が外部で適切に同期する *ことを条件に*、複数のスレッドから安
全に呼び出せる関数や操作です。 たとえば、呼び出しのたびに、呼び出しが
続く間 *ロック* を保持して同期します。 こうした同期がないと、並行呼び
出しによって *競合状態* や *データ競合* が発生する可能性があります。

例: 内部状態がロックで保護されていないオブジェクトを読み書きする関数で
す。 呼び出し側は、二つのスレッドが同じオブジェクトに同時にアクセスし
ないことを保証しなければなりません。


異なるオブジェクトなら安全
--------------------------

各スレッドが **異なる** オブジェクトを操作する限り、外部で同期せずに複
数のスレッドから安全に呼び出せる関数や操作です。 二つのスレッドが同時
にこの関数を呼び出してもかまいませんが、同じオブジェクト (または内部の
状態を共有するオブジェクト) を引数に渡してはなりません。

例: アトミックでない書き込みを使って、構造体のフィールドを変更する関数
です。 二つのスレッドがそれぞれ自分の構造体インスタンスに対してこの関
数を呼び出すのは安全ですが、*同じ* インスタンスに対する並行呼び出しに
は、外部での同期が必要です。


共有オブジェクトでも安全
------------------------

**同じ** オブジェクトに対して安全に並行使用できる関数や操作です。 実装
が内部で同期 (*オブジェクトごとのロック* や クリティカルセクション な
ど) を行って共有された可変の状態を保護するため、呼び出し側で独自にロッ
クを用意する必要はありません。

Example: "PyList_GetItemRef()" can be called from multiple threads on
the same "PyListObject" - it uses internal synchronization to
serialize access.


アトミック
----------

ほかのスレッドから見ると *アトミック* に実行される関数や操作です。 つ
まり、ほかのスレッドの視点では、一瞬で実行されるように見えます。 これ
はスレッドセーフ性の最も強い保証です。

例: "PyMutex_IsLocked()" はミューテックスの状態をアトミックに読み取り
、どのスレッドからでも、いつでも呼び出せます。


"list" オブジェクトのスレッドセーフ性
=====================================

"list" からの単一要素の読み取りは *アトミック* です。

   lst[i]   # list.__getitem__

次のメソッドはリストを走査し、各要素を *アトミック* に読み取って処理を
行います。 そのため、並行して行われる変更の影響を受けた結果を返すこと
があります。

   item in lst
   lst.index(item)
   lst.count(item)

All of the above operations avoid acquiring *per-object locks*. They
do not block concurrent modifications. Other operations that hold a
lock will not block these from observing intermediate states.

これ以降のほかの操作はすべて、*オブジェクトごとのロック* を使ってブロ
ックします。

"lst[i] = x" による単一要素の書き込みは、複数のスレッドから安全に実行
でき、リストを破損させません。

次の操作は新しいオブジェクトを返し、ほかのスレッドから見ると *アトミッ
ク* に実行されます。

   lst1 + lst2    # 二つのリストを結合して新しいリストを作成する
   x * lst        # lst を x 回繰り返して新しいリストを作成する
   lst.copy()     # リストの浅いコピーを返す

次のメソッドは、要素をずらす必要がなく、単一の要素だけを操作するため、
*アトミック* です。

   lst.append(x)  # リストの末尾に追加するため、要素をずらす必要はない
   lst.pop()      # リストの末尾の要素を取り出して削除するため、要素をずらす必要はない

"clear()" メソッドも *アトミック* です。 ほかのスレッドから、要素が削
除されていく途中の状態は観測できません。

"sort()" メソッドは *アトミック* ではありません。 ほかのスレッドからソ
ート途中の状態は観測できませんが、ソート中のリストは空に見えます。

次の操作は複数の要素をその場で変更するため、*ロックフリーな* 操作から
途中の状態が観測されることがあります。

   lst.insert(idx, item)  # 要素をずらす
   lst.pop(idx)           # idx がリストの末尾でなければ、要素をずらす
   lst *= x               # 要素をその場でコピーする

要素の比較で ("__eq__()" を通じて) 任意の Python コードを実行する可能
性があるため、"remove()" メソッドの実行中に並行して変更が行われること
があります。

"extend()" は複数のスレッドから安全に呼び出せます。 ただし、その保証は
渡されたイテラブルによって異なります。 渡されたイテラブルが "list"、
"tuple"、"set"、"frozenset"、"dict"、辞書ビューオブジェクト のいずれか
(サブクラスは除く) である場合、"extend" 操作はそのイテラブルに対する並
行変更から保護されます。 それ以外の場合は、ほかのスレッドによって並行
して変更される可能性があるイテレータが作成されます。 "lst += iterable"
を使ってリストとほかのイテラブルをインプレースに結合する場合も、同じこ
とが当てはまります。

同様に、"lst[i:j] = iterable" によるリストのスライスへの代入も、複数の
スレッドから安全に実行できますが、"iterable" がロックされるのは、
"iterable" も "list" (サブクラスは除く) である場合だけです。

複数回のアクセスを伴う操作やイテレーションは、アトミックではありません
。 たとえば、次のような操作です。

   # アトミックではない: 読み取り、変更、書き込み
   lst[i] = lst[i] + 1

   # アトミックではない: 確認してから実行
   if lst:
       item = lst.pop()

   # スレッドセーフではない: 変更中のイテレーション
   for item in lst:
       process(item)  # ほかのスレッドが lst を変更する可能性がある

スレッド間で "list" インスタンスを共有する場合は、外部での同期を検討し
てください。


"dict" オブジェクトのスレッドセーフ性
=====================================

Creating a dictionary with the "dict" constructor is atomic when the
argument to it is a "dict" or a "tuple". When using the
"dict.fromkeys()" method, dictionary creation is atomic when the
argument is a "dict", "tuple", "set" or "frozenset".

The following operations and functions are *lock-free* and *atomic*.

   d[key]       # dict.__getitem__
   d.get(key)   # dict.get
   key in d     # dict.__contains__
   len(d)       # dict.__len__

All other operations from here on hold the *per-object lock*.

単一の要素の書き込みや削除は、複数のスレッドから安全に実行でき、辞書を
破損させません。

   d[key] = value        # 書き込む
   del d[key]            # 削除する
   d.pop(key)            # 削除して返す
   d.popitem()           # 最後の要素を削除して返す
   d.setdefault(key, v)  # 存在しなければ挿入する

これらの操作は "__eq__()" を使ってキーを比較することがあり、その際に任
意の Python コードを実行する可能性があります。 このような比較中に、ほ
かのスレッドが辞書を変更する可能性があります。 "__eq__()" を C で実装
している "str"、"int"、"float" などの組み込み型では、比較中に内部のロ
ックを解放しないため、この心配はありません。

The following operations return new objects and hold the *per-object
lock* for the duration of the operation:

   d.copy()      # 辞書の浅いコピーを返す
   d | other     # 二つの辞書をマージして新しい辞書を作成する
   d.keys()      # 新しい dict_keys ビューオブジェクトを返す
   d.values()    # 新しい dict_values ビューオブジェクトを返す
   d.items()     # 新しい dict_items ビューオブジェクトを返す

"clear()" メソッドは、処理中ずっとロックを保持します。 ほかのスレッド
から、要素が削除されていく途中の状態は観測できません。

次の操作は両方の辞書をロックします。 "update()" と "|=" では、もう一方
のオペランドが標準の辞書イテレータを使う "dict" である場合だけ、この保
証が適用されます (イテレーションをオーバーライドするサブクラスは除きま
す)。 等値比較では、"dict" とそのサブクラスにこの保証が適用されます。

   d.update(other_dict)  # other_dict が dict の場合は両方をロックする
   d |= other_dict       # other_dict が dict の場合は両方をロックする
   d == other_dict       # dict とそのサブクラスの場合は両方をロックする

比較操作はすべて、値の比較にも "__eq__()" を使うため、組み込み型でない
場合は比較中にロックを解放することがあります。

イテラブルが "dict"、"set"、"frozenset" そのもの (サブクラスは除く) で
ある場合、"fromkeys()" は新しい辞書とそのイテラブルの両方をロックしま
す。

   dict.fromkeys(a_dict)      # 両方をロックする
   dict.fromkeys(a_set)       # 両方をロックする
   dict.fromkeys(a_frozenset) # 両方をロックする

辞書ではないイテラブルから更新する場合、更新先の辞書だけをロックします
。 イテラブルは、ほかのスレッドによって並行して変更される可能性があり
ます。

   d.update(iterable)        # iterable が dict ではない: d だけをロックする
   d |= iterable             # iterable が dict ではない: d だけをロックする
   dict.fromkeys(iterable)   # iterable が dict/set/frozenset ではない: 結果だけをロックする

複数回のアクセスを伴う操作やイテレーションは、アトミックではありません
。

   # アトミックではない: 読み取り、変更、書き込み
   d[key] = d[key] + 1

   # アトミックではない: 確認してから実行 (TOCTOU)
   if key in d:
       del d[key]

   # スレッドセーフではない: 変更中のイテレーション
   for key, value in d.items():
       process(key)  # ほかのスレッドが d を変更する可能性がある

確認から使用までの間に状態が変わる問題 (TOCTOU: time-of-check to time-
of-use) を避けるには、アトミックな操作を使うか、例外を処理してください
。

   # 確認してから削除する代わりに、デフォルト値付きの pop() を使う
   d.pop(key, None)

   # または例外を処理する
   try:
       del d[key]
   except KeyError:
       pass

ほかのスレッドによって変更される可能性のある辞書を安全にイテレートする
には、コピーをイテレートしてください。

   # 安全にイテレートするためにコピーを作成する
   for key, value in d.copy().items():
       process(key)

スレッド間で "dict" インスタンスを共有する場合は、外部での同期を検討し
てください。


"set" オブジェクトのスレッドセーフ性
====================================

"len()" 関数はロックフリーで、*アトミック* です。

The following read operation is lock-free. It does not block
concurrent modifications and may observe intermediate states from
operations that hold the per-object lock:

   elem in s    # set.__contains__

This operation may compare elements using "__eq__()", which can
execute arbitrary Python code. During such comparisons, the set may be
modified by another thread. For built-in types like "str", "int", and
"float", "__eq__()" does not release the underlying lock during
comparisons and this is not a concern.

これ以降のほかの操作はすべて、オブジェクトごとのロックを保持します。

単一の要素の追加や削除は、複数のスレッドから安全に実行でき、集合を破損
させません。

   s.add(elem)      # 要素を追加する
   s.remove(elem)   # 要素を削除し、存在しなければ例外を送出する
   s.discard(elem)  # 要素が存在すれば削除する
   s.pop()          # 任意の要素を削除して返す

これらの操作も要素を比較するため、"__eq__()" についての上記と同じ注意
事項が当てはまります。

"copy()" メソッドは新しいオブジェクトを返し、処理中ずっとオブジェクト
ごとのロックを保持するため、常にアトミックです。

"clear()" メソッドは、処理中ずっとロックを保持します。 ほかのスレッド
から、要素が削除されていく途中の状態は観測できません。

The following operations only accept "set" or "frozenset" as operands
and always lock both objects:

   s |= other                   # other は set/frozenset でなければならない
   s &= other                   # other は set/frozenset でなければならない
   s -= other                   # other は set/frozenset でなければならない
   s ^= other                   # other は set/frozenset でなければならない
   s & other                    # other は set/frozenset でなければならない
   s | other                    # other は set/frozenset でなければならない
   s - other                    # other は set/frozenset でなければならない
   s ^ other                    # other は set/frozenset でなければならない

"set.update()"、"set.union()"、"set.intersection()"、
"set.difference()" は、引数として複数のイテラブルを受け取れます。 これ
らはいずれも、渡されたすべてのイテラブルをイテレートし、次の処理を行い
ます。

   * "set.update()" and "set.union()" lock both objects only when the
     other operand is a "set", "frozenset", or "dict".

   * "set.intersection()" と "set.difference()" は、常にすべてのオブジ
     ェクトのロックを試みます。

"set.symmetric_difference()" は両方のオブジェクトのロックを試みます。

上記のメソッドに対応する更新用メソッドにも、次のような違いがあります。

   * "set.difference_update()" と "set.intersection_update()" は、すべ
     てのオブジェクトを一つずつロックしようとします。

   * "set.symmetric_difference_update()" は、引数の型が "set"、
     "frozenset"、"dict" のいずれかである場合だけ、その引数をロックし
     ます。

次のメソッドは、常に両方のオブジェクトのロックを試みます。

   s.isdisjoint(other)          # 両方をロックする
   s.issubset(other)            # 両方をロックする
   s.issuperset(other)          # 両方をロックする

複数回のアクセスを伴う操作やイテレーションは、アトミックではありません
。

   # アトミックではない: 確認してから実行
   if elem in s:
         s.remove(elem)

   # スレッドセーフではない: 変更中のイテレーション
   for elem in s:
         process(elem)  # ほかのスレッドが s を変更する可能性がある

スレッド間で "set" インスタンスを共有する場合は、外部での同期を検討し
てください。 詳細については、Python のフリースレッド対応 を参照してく
ださい。


"bytearray" オブジェクトのスレッドセーフ性
==========================================

   "len()" 関数はロックフリーで、*アトミック* です。

   結合と比較はバッファプロトコルを使います。 バッファプロトコルはサイ
   ズの変更を防ぎますが、オブジェクトごとのロックは保持しません。 これ
   らの操作は、並行して行われる変更の途中の状態を観測することがありま
   す。

      ba + other    # 並行して行われる書き込みを観測する可能性がある
      ba == other   # 並行して行われる書き込みを観測する可能性がある
      ba < other    # 並行して行われる書き込みを観測する可能性がある

   これ以降のほかの操作はすべて、オブジェクトごとのロックを保持します
   。

   単一の要素やスライスの読み取りは、複数のスレッドから安全に実行でき
   ます。

      ba[i]        # bytearray.__getitem__
      ba[i:j]      # スライス

   次の操作は複数のスレッドから安全に実行でき、バイト配列を破損させま
   せん。

      ba[i] = x         # 単一のバイトを書き込む
      ba[i:j] = values  # スライスに書き込む
      ba.append(x)      # 単一のバイトを末尾に追加する
      ba.extend(other)  # イテラブルを使って拡張する
      ba.insert(i, x)   # 単一のバイトを挿入する
      ba.pop()          # 最後のバイトを削除して返す
      ba.pop(i)         # 指定のインデックスのバイトを削除して返す
      ba.remove(x)      # 最初に現れた値を削除する
      ba.reverse()      # その場で順序を反転する
      ba.clear()        # すべてのバイトを削除する

   "values" が "bytearray" の場合、スライスへの代入は両方のオブジェク
   トをロックします。

      ba[i:j] = other_bytearray  # 両方をロックする

   次の操作は新しいオブジェクトを返し、処理中ずっとオブジェクトごとの
   ロックを保持します。

      ba.copy()     # 浅いコピーを返す
      ba * n        # 繰り返して新しいバイト配列を作成する

   要素が含まれるかどうかの判定では、処理中ずっとロックを保持します。

      x in ba       # bytearray.__contains__

   All other bytearray methods (such as "find()", "replace()",
   "split()", "decode()", etc.) hold the per-object lock for their
   duration.

   複数回のアクセスを伴う操作やイテレーションは、アトミックではありま
   せん。

      # アトミックではない: 確認してから実行
      if x in ba:
          ba.remove(x)

      # スレッドセーフではない: 変更中のイテレーション
      for byte in ba:
          process(byte)  # ほかのスレッドが ba を変更する可能性がある

   ほかのスレッドによって変更される可能性のあるバイト配列を安全にイテ
   レートするには、コピーをイテレートしてください。

      # 安全にイテレートするためにコピーを作成する
      for byte in ba.copy():
          process(byte)

   スレッド間で "bytearray" インスタンスを共有する場合は、外部での同期
   を検討してください。 詳細については、Python のフリースレッド対応 を
   参照してください。


"memoryview" オブジェクトのスレッドセーフ性
===========================================

"memoryview" オブジェクトは、参照先のオブジェクトの内部データに、コピ
ーせずにアクセスする手段を提供します。 スレッドセーフ性は、メモリービ
ュー自体と、参照先のバッファを提供するオブジェクトの両方によって決まり
ます。

The memoryview implementation uses atomic operations to track its own
exports in the *free-threaded build*. Creating and releasing a
memoryview are thread-safe. Attribute access (e.g., "shape", "format")
reads fields that are immutable for the lifetime of the memoryview, so
concurrent reads are safe as long as the memoryview has not been
released.

ただし、メモリービューを通じてアクセスする実際のデータは、参照先のオブ
ジェクトが所有しています。 このデータへの並行アクセスが安全なのは、参
照先のオブジェクトがそれをサポートしている場合だけです。

* "bytes" のような不変のオブジェクトでは、複数のメモリービューを通じて
  並行して読み取っても安全です。

* "bytearray" のような可変のオブジェクトでは、外部で同期せずに、複数の
  スレッドから同じメモリー領域を読み書きするのは安全ではなく、データの
  破損を引き起こす可能性があります。 可変のオブジェクトに対する読み取
  り専用のメモリービューであっても、ほかのスレッドから参照先のオブジェ
  クトが変更されると、データ競合を防げないことに注意してください。

   # 安全ではない: 同じバッファへの並行書き込み
   data = bytearray(1000)
   view = memoryview(data)
   # スレッド 1: view[0:500] = b'x' * 500
   # スレッド 2: view[0:500] = b'y' * 500

   # 安全: 並行アクセスにはロックを使う
   import threading
   lock = threading.Lock()
   data = bytearray(1000)
   view = memoryview(data)

   with lock:
       view[0:500] = b'x' * 500

メモリービューによってバッファが公開されている間に、参照先のオブジェク
トのサイズを変更したり、メモリーを再割り当てしたりすると
("bytearray.resize()" の呼び出しなど)、"BufferError" が送出されます。
これは、スレッドの使用に関係なく適用されます。
