GILなしのPythonが来た: free-threadingの現状と使うべきタイミング

読了 8分

モダンPython上級 #5 で、GIL のせいで CPU バウンドの作業にはスレッドが無意味だと整理しました。コアが 8 個あっても Python のスレッドは一度に 1 つしかバイトコードを実行できないため、CPU を使い切るには multiprocessing でプロセスを立てるのが正解でした。ところが、この前提が今変わりつつあります。GIL のない Python ビルドが 3.13 で実験として登場し、3.14 で公式サポート段階に入りました。この記事では free-threading とは何か、2026 年半ば時点でどこまで来ているのか、そして今使うべきかを判断できるように整理します。

free-threading とは何か #

PEP 703 が提案した変更です。一言で要約すると、GIL を取り除いてもインタプリタが安全に動作するように CPython 内部を直す作業です。

GIL が守っていたのは、オブジェクト参照カウントの一貫性でした。ロックをただ外すと、複数のスレッドが同時に参照カウントを触ってメモリが壊れます。そこで free-threaded ビルドは、参照カウントの方式自体を変えました。スレッドごとにカウントを分けて管理する biased reference counting、オブジェクト単位の細かいロック、ロックなしで読める内部データ構造といった仕組みが、GIL の仕事を肩代わりします。

結果として、外から見える動作は同じです。threading.Thread を使うコードはそのまま動きます。変わるのは 1 つです。CPU バウンドのマルチスレッディングが本当に並列実行されます

タイムライン: 実験から公式サポートまで #

  • Python 3.13 (2024 年 10 月): free-threaded ビルドが実験 (experimental) 段階として初めて含まれました。3.13t という別ビルドで提供され、シングルスレッドのオーバーヘッドが大きい状態でした。
  • Python 3.14 (2025 年 10 月): PEP 779公式サポート (officially supported) 段階に入りました。実験のラベルが外れ、適応型インタプリタ (PEP 659) の最適化が free-threaded モードでも有効になり、性能が大きく改善しました。

注意点が 1 つあります。公式サポートになったからといって、デフォルトビルドが変わったわけではありません。free-threaded は依然として別ビルドで、opt-in です。python.org からダウンロードするデフォルトのインストーラは、今も GIL ありのビルドです。GIL ビルドを完全に置き換える最終段階は、まだスケジュールが決まっていません。

インストールと確認 #

バージョンの後ろに t が付いたものが free-threaded ビルドです。uv を使えば 1 行でインストールできます。

free-threaded ビルドのインストール
# free-threaded ビルドのインストール
uv python install 3.14t

# プロジェクトに指定
uv init my-app --python 3.14t

# ビルドの確認 (free-threading build の文言が表示されます)
python -VV

実行中のプロセスで GIL が本当にオフになっているかは、コードで確認します。

GIL 状態の確認
import sys

print(sys._is_gil_enabled())   # False なら GIL なしで動作中

この確認が重要な理由があります。free-threading をサポートしない C 拡張を import すると、インタプリタはプロセス全体の GIL を静かに再び有効にします。エラーも警告もなく、GIL ビルドのように動作するようになります。そのため、依存関係をすべて import した後に sys._is_gil_enabled() で点検する習慣が必要です。環境変数 PYTHON_GIL=1 または実行オプション -X gil=1 で、free-threaded ビルドで GIL を強制的に有効にすることもできます。

何が速くなるのか #

CPU バウンドの作業をスレッドに分けると、コア数に比例した加速を期待できます。

free-threaded で本当に並列になるコード
from concurrent.futures import ThreadPoolExecutor

def count_primes(limit):
    count = 0
    for n in range(2, limit):
        if all(n % d for d in range(2, int(n ** 0.5) + 1)):
            count += 1
    return count

with ThreadPoolExecutor(max_workers=8) as pool:
    results = list(pool.map(count_primes, [200_000] * 8))

GIL ビルドでは、このコードはスレッド 8 個が事実上シリアルに回り、1 個のときと同じくらいの時間がかかります。free-threaded ビルドでは 8 個のコアが同時に働き、コア数に近い倍率で速くなります。

multiprocessing と比べたときの利点も明確です。プロセス生成のコストがなく、データを pickle でシリアライズしてやり取りする必要がありません。大きな配列やモデルを、スレッド同士が同じメモリでそのまま共有します。CPU 並列とデータ共有を同時に求めていたワークロードにとっては、構造的な改善です。

代償 1: シングルスレッドのオーバーヘッド #

GIL はシンプルなぶん、速い同期装置でもありました。これを細かいロックと分散された参照カウントで置き換えると、スレッドを 1 つだけ使うときの性能はかえって落ちます。

3.14 時点でシングルスレッドのオーバーヘッドは、プラットフォームによりおよそ 5〜10% 程度です。pyperformance ベンチマークの平均で見ると、macOS aarch64 で約 1%、x86-64 Linux で約 8% と、プラットフォームによる差があります。3.13 の実験ビルドの頃は適応型インタプリタが無効だったためこれよりはるかに大きかったのですが、3.14 で大きく縮まり、以降のバージョンでも縮め続けるのが CPython チームの方向性です。

解釈するとこうなります。スレッドを 1 つしか使わないプログラムなら、free-threaded ビルドは損しかありません。マルチコアの加速がこのオーバーヘッドを相殺して余りあるときだけ得になります。

代償 2: C 拡張エコシステムの過渡期 #

純粋な Python コードはほとんどそのまま動きます。問題は C 拡張です。30 年間 GIL がある前提で書かれてきた拡張が thread-safety を自前で保証するように直される必要があり、free-threaded ビルド用の wheel (cp314t) を別途ビルドして配布しなければなりません。

2026 年半ば時点の状況は次のとおりです。

  • NumPy: 2.4 系が free-threading サポートを継続的に改善中で、cp314t wheel を主要プラットフォームに提供しています。
  • PyTorch: 2.9 でプレビュー wheel を経て、2.10.0 (2026 年 1 月) から正式な cp314t wheel を提供しています。
  • 未対応パッケージ: grpcio のように、まだ cp314t wheel がない基盤パッケージもあります。こうしたパッケージに依存するプロジェクトは、その上の全体が止まります。

エコシステム全体の互換状況は、py-free-threading.github.io のトラッキングテーブルでパッケージごとに確認できます。中核の数値計算ライブラリはほとんど移行が済み、ロングテールの小さな C 拡張が残っている状態だと要約できます。

今プロダクションで使うべきか #

判断基準を整理します。

まだデフォルトビルドが答えである場合がほとんどです

  • Web API サーバー、スクリプト、I/O バウンドのワークロードでは、free-threading の利得がほとんどありません。オーバーヘッドだけを負担します。
  • 依存ツリーに C 拡張が多く、そのうち 1 つでも未対応なら GIL が静かに再び有効になるため、導入効果自体が消えます。
  • 運用環境のモニタリング、プロファイリングツールが free-threaded ビルドをサポートしているかも、別途確認が必要です。

試す価値がある場合も確かにあります

  • マルチコアの CPU 演算と大きな共有データの両方が必要なワークロード。multiprocessing の IPC コストに苦しんでいたコードが代表例です。
  • 依存関係が少ない、または NumPy、PyTorch のようにすでに互換対応を終えたライブラリ中心のプロジェクト。
  • 新しく始めるプロジェクトで並行性の設計を検証してみたい場合。uv python install 3.14t の 1 行で隔離された実験環境ができるため、コストは低く済みます。

要約すると、全面移行は早く、ワークロードを選んで実験する段階です。導入するなら、デプロイ前に sys._is_gil_enabled() の点検と、マルチスレッド環境での race condition テストを一緒に入れるべきです。GIL が隠していた並行性バグが、free-threaded で表面化する場合があるためです。

multiprocessing と asyncio はどうなるのか #

free-threading がデフォルトになる未来でも、2 つの道具がなくなることはありません。役割が狭くなるだけです。multiprocessing はプロセス分離自体が目的の場合 (クラッシュの分離、メモリ上限の分離、別のインタプリタ状態) に残り、「CPU 並列のためにやむを得ず」使っていた用法はスレッドに移る可能性が大きいです。asyncio は GIL と無関係に、大規模 I/O 並行性という自分の領域がそのまま残ります。むしろ free-threaded の上でイベントループを複数、スレッドごとに回す組み合わせが新しく開ける側に近いです。道具の選択基準が「GIL を避ける方法」から「ワークロードの性格に合う方法」へと単純になる変化だと言えます。

まとめ #

  • free-threading は、GIL を取り除いても安全に動作するように CPython を直した別ビルドです (PEP 703)。
  • 3.13 で実験段階として導入され、3.14 で PEP 779 により公式サポート段階になりました。デフォルトビルドは依然として GIL ビルドです。
  • uv python install 3.14t でインストールし、import の後に sys._is_gil_enabled() で GIL が本当にオフかを確認します。
  • CPU バウンドのマルチスレッディングがコア数に比例して速くなり、multiprocessing のシリアライズコストなしでメモリを共有します。
  • 代償は、約 5〜10% のシングルスレッドオーバーヘッド (プラットフォームによる差あり) と、C 拡張エコシステムの過渡期です。
  • 今は全面移行よりワークロードを選んで実験する段階で、CPU 並列 + 共有データのワークロードから試す価値があります。

GIL の一般論と threading/multiprocessing/asyncio の分担は モダンPython上級 #5 で整理しているので、背景が必要ならその記事を先に読むことをおすすめします。

X