Python のフリースレッド対応
***************************

CPython 3.13 から、*global interpreter lock* (GIL) を無効にした *free
threading* という Python のビルドがサポートされています。 フリースレッ
ド実行では、利用可能な CPU コアでスレッドを並列に動かし、処理能力を十
分に活用できます。 すべてのソフトウェアが自動的に恩恵を受けるわけでは
ありませんが、スレッドの利用を考慮して設計されたプログラムは、マルチコ
アのハードウェア上でより速く動作します。

一部のサードパーティーパッケージ、特に *extension module* を含むものは
、フリースレッドビルドへの対応ができておらず、*GIL* を再び有効にする場
合があります。

このドキュメントでは、フリースレッドが Python コードに与える影響を説明
します。 フリースレッドビルドに対応した C 拡張の書き方は、C API 拡張の
フリースレッド対応 を参照してください。

参考:

  フリースレッドの Python の全体的な説明は、**PEP 703** — Making the
  Global Interpreter Lock Optional in CPython を参照してください。


インストール
============

Python 3.13 から、macOS と Windows の公式インストーラでは、フリースレ
ッド版 Python のバイナリを追加でインストールできます。 インストーラは
https://www.python.org/downloads/ から入手できます。

ほかのプラットフォームについては、コミュニティが保守しているフリースレ
ッド版 Python のインストールガイド、Installing a Free-Threaded Python
を参照してください。

CPython をソースからビルドする場合、フリースレッドの Python インタープ
リタを作るには configure の "--disable-gil" オプションを使います。


フリースレッド版 Python の識別
==============================

実行中のインタープリタがフリースレッドに対応しているかは、"python -VV"
の出力や "sys.version" に "free-threading build" が含まれるかで確認で
きます。 新しい "sys._is_gil_enabled()" 関数では、実行中のプロセスで
GIL が実際に無効になっているかを確認できます。

"sysconfig.get_config_var("Py_GIL_DISABLED")" 設定変数でも、ビルドがフ
リースレッドに対応しているかを確認できます。 値が "1" なら対応していま
す。 ビルド構成に関する判断には、この方法を使うことが推奨されます。


フリースレッド版 Python のグローバルインタープリタロック
========================================================

CPython のフリースレッドビルドでは、"PYTHON_GIL" 環境変数または "-X
gil" コマンドラインオプションを使い、実行時に GIL を有効にすることもで
きます。

フリースレッド対応が明示されていない C API 拡張モジュールをインポート
すると、GIL が自動的に有効になることもあります。 この場合は警告が表示
されます。

個々のパッケージのドキュメントに加え、次のウェブサイトでも、よく使われ
るパッケージのフリースレッド対応状況を確認できます。

* https://py-free-threading.github.io/tracking/

* https://hugovk.github.io/free-threaded-wheels/


スレッド安全性
==============

CPython のフリースレッドビルドは、Python レベルでは、デフォルトの GIL
が有効なビルドと同様のスレッド安全性を提供することを目指しています。
"dict"、"list"、"set" などの組み込み型は内部ロックを使い、GIL と似た動
作で並行する変更から保護します。 組み込み型が提供する保証については、
スレッドセーフ性の保証 を参照してください。

注釈:

  可能であれば、組み込み型の内部ロックに頼る代わりに、"threading.Lock"
  などの同期プリミティブを使うことが推奨されます。


既知の制限
==========

この節では、CPython のフリースレッドビルドの既知の制限を説明します。


不死化
------

フリースレッドビルドでは、一部のオブジェクトが *immortal* になります。
不死のオブジェクトは解放されず、参照カウントも変更されません。 これは
、効率的なマルチスレッドでの性能向上を妨げる、参照カウントの競合を避け
るためです。

3.14 リリース時点では、不死化されるものは次に限られます。

* コード内の定数: 数値リテラル、文字列リテラル、ほかの定数からなるタプ
  ルリテラル。

* "sys.intern()" でインターンされた文字列。


フレーム (frame) オブジェクト
-----------------------------

別のスレッドで実行中の フレーム オブジェクトの "frame.f_locals" にアク
セスするのは安全ではなく、インタープリタがクラッシュする可能性がありま
す。


イテレータ (iterator)
---------------------

一般に、同じイテレータオブジェクトに複数のスレッドから並行してアクセス
することは、スレッドセーフではありません。 要素が重複して取得されたり
、取得できない要素が出たりする可能性があります。


シングルスレッドの性能
----------------------

フリースレッドビルドは、デフォルトの GIL が有効なビルドに比べ、Python
コードの実行に追加のオーバーヘッドがあります。 その大きさはワークロー
ドとハードウェアに依存します。 pyperformance ベンチマークスイートでは
、平均のオーバーヘッドは macOS aarch64 の約 1% から x86-64 Linux の約
8% までの範囲です。


動作の違い
==========

この節では、フリースレッドビルドによる CPython の動作の違いを説明しま
す。


コンテキスト変数
----------------

フリースレッドビルドでは、"thread_inherit_context" フラグはデフォルト
で真です。 そのため、"threading.Thread" で作られたスレッドは、
"start()" の呼び出し元の "Context()" のコピーを持って開始します。 デフ
ォルトの GIL が有効なビルドでは、このフラグはデフォルトで偽なので、ス
レッドは空の "Context()" で開始します。


警告フィルタ
------------

フリースレッドビルドでは、"context_aware_warnings" フラグはデフォルト
で真です。 デフォルトの GIL が有効なビルドでは、偽になっています。 真
の場合、"warnings.catch_warnings" コンテキストマネージャは、警告フィル
タにコンテキスト変数を使います。 偽の場合、"catch_warnings" はグローバ
ルなフィルタのリストを変更するため、スレッドセーフではありません。 詳
しくは "warnings" モジュールを参照してください。


メモリ使用量の増加
------------------

フリースレッドビルドは、通常、デフォルトのビルドより多くのメモリを使い
ます。 理由はいくつかあり、その多くは設計上の判断によるものです。


インターンされた文字列はすべて不死になる
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Python 2.3 以降では、"sys.intern()" などで文字列をインターンしても、不
死にはなりません。 その文字列への最後の参照がなくなると、インターン済
み文字列のテーブルから削除されます。 フリースレッドビルドではこれとは
異なり、インターンされた文字列はすべて不死になり、インタープリタの終了
まで生存します。


GC の対象でないオブジェクトのヘッダが大きくなる
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

フリースレッドビルドは、異なる "PyObject" 構造体を使います。 デフォル
トのビルドのように GC 関連の情報を "PyObject" 構造体の前に配置するので
はなく、通常のオブジェクトヘッダの一部にしています。 例えば AMD64 プラ
ットフォームでは、"None" はフリースレッドビルドで 32 バイト、デフォル
トのビルドでは 16 バイトを使います。 辞書やリストなど、GC 対象のオブジ
ェクトのサイズは両ビルドで同じです。 フリースレッドビルドでは、GC 情報
のために別の領域を使わないためです。


QSBR によってメモリの解放が遅れることがある
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

ロックを使わないデータ構造を安全に実装するため、quiescent state-based
reclamation (QSBR) という安全なメモリ回収 (SMR) の方式を使います。 ロ
ックなしのアクセスを許すデータ構造の領域には QSBR が使われ、メモリはす
ぐには解放されず、解放が遅延されます。 例として、リストオブジェクトや
辞書のキーオブジェクトがあります。 QSBR の実装の詳細は、CPython ソース
ツリーの "InternalDocs/qsbr.md" を参照してください。 "gc.collect()" を
実行すると、QSBR によって保持されているメモリはすべて解放されるはずで
す。 ただし、QSBR がメモリを解放しても、下位のメモリアロケータがすぐに
OS へ返すとは限らず、プロセスの常駐メモリ量 (RSS) が減らない場合があり
ます。


mimalloc と pymalloc の比較
~~~~~~~~~~~~~~~~~~~~~~~~~~~

デフォルトのビルドは通常、512 バイト以下の小さな割り当てに pymalloc メ
モリアロケータを使います。 フリースレッドビルドは pymalloc を使わず、
すべての Python オブジェクトを mimalloc で割り当てます。 pymalloc には
、割り当てブロックごとのオーバーヘッドが小さい、メモリの断片化を効果的
に防ぐ、未使用メモリを速やかに OS へ返すといった、メモリ使用量を抑える
特性があります。 mimalloc もこれらの点で優れていますが、オーバーヘッド
がやや大きい場合があります。

フリースレッドビルドでは、mimalloc は複数の独立したヒープ (現在は 4 つ
) でメモリを管理します。 例えば、GC に対応するオブジェクトはすべて専用
のヒープから割り当てられます。 ヒープを分けると、あるヒープの空きメモ
リを別のヒープからの割り当てに使うことはできません。 また、一部のヒー
プでは、ヒープの基盤となるメモリ領域 (mimalloc の用語で「ページ」) の
解放に QSBR (quiescent-state based reclamation) を使います。 そのため
、ページ内の全メモリブロックが解放されてから、そのページが新しい割り当
てに使えるようになるか OS へ返されるまでに、遅延が生じます。

mimalloc も、解放されたメモリを OS に返すまでに遅延を設けています。
"MIMALLOC_PURGE_DELAY" 環境変数を "0" に設定すると、この遅延を短くでき
ます。 ただし、アロケータの性能が低下する可能性が高いことに注意してく
ださい。


フリースレッドの参照カウントではオブジェクトの寿命が延びることがある
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

デフォルトのビルドでは、通常、参照カウントがゼロになるとオブジェクトが
解放されます。 フリースレッドビルドは「バイアス参照カウント」を使い、
現在のスレッドが「所有する」オブジェクトには高速な処理経路、それ以外に
は低速な経路を使います。 詳しくは **PEP 703** を参照してください。 参
照カウントが queued (キュー待ち) 状態になると、解放が遅延することがあ
ります。 キュー待ち状態は、バイトコード評価器の eval breaker 部分で解
除されます。

フリースレッドビルドでは、「遅延参照カウント」という別の方式も使えます
。 これはオブジェクトごとにフラグを設定して有効にします。 次の型で遅延
参照カウントが有効になっています。

* モジュールオブジェクト

* モジュールのトップレベルの関数

* クラススコープで定義されたメソッド

* デスクリプタオブジェクト

* "threading.local" で作られたスレッドローカルオブジェクト

遅延参照カウントを有効にすると、Python 関数のスタックからの参照は参照
カウントに加算されません。 この方式は、特に複数のスレッドから使われる
オブジェクトについて、参照カウントのオーバーヘッドを減らします。 スタ
ックからの参照を数えないため、内部の参照カウントがゼロになっても、オブ
ジェクトはすぐには解放されません。 代わりに、次回の GC で調べられ、ス
タックからの参照がなければ解放されます。 つまり、通常のように参照カウ
ントがゼロになった時点ではなく、GC によって解放されます。


スレッドごとの参照カウントによってオブジェクトの解放が遅れることがある
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

頻繁に共有されるオブジェクトの参照カウントフィールドでの競合を避けるた
め、フリースレッドビルドは、選ばれた少数の型に「スレッドごとの参照カウ
ント」も使います。 単一の共有参照カウントを更新する代わりに、各スレッ
ドがローカルな参照カウントの配列を持ち、オブジェクトに割り当てられた一
意の ID をインデックスとして使います。 実際の参照カウントを計算するの
は、オブジェクトのローカルカウントがゼロになったときだけで、その際に各
スレッドのカウントを合計します。 現在、この方式を使うのは次のものです
。

* ヒープ型オブジェクト (Python で作成したクラス)

* コードオブジェクト

* モジュールオブジェクトの "__dict__"

スレッドごとのカウントをオブジェクトに集約してからでなければ解放できな
いため、この方式を使うオブジェクトは、通常、デフォルトのビルドより遅く
解放されます。 特に、そのオブジェクトを参照したスレッドが、バイトコー
ド評価器の eval breaker などの安全な地点に到達するか、終了するまでは、
通常は解放されません。 "gc.collect()" を実行すると、スレッドごとのカウ
ントが集約され、これらのオブジェクトを解放できるようになります。
