Pythonの辞書はスレッドセーフ?GILの仕組みとマルチスレッドでの注意点
Pythonの辞書はスレッドセーフなのか?
結論から言うと、Pythonの辞書(dict)は基本的にスレッドセーフです。さらに言えば、辞書だけでなく、リストやセットなどPythonの組み込み型は、単一の操作レベルであればすべてスレッドセーフであると言えます。
なぜスレッドセーフなのか:GIL(グローバルインタプリタロック)
その理由は、CPythonが採用しているGIL(Global Interpreter Lock:グローバルインタプリタロック)という仕組みにあります。GILは、一度に1つのスレッドだけがPythonバイトコードを実行できるように制御するロックであり、このおかげで複数のスレッドが同時に同じオブジェクトの内部状態を壊してしまうことが防がれています。
つまり、あるスレッドがd[key] = valueのような辞書操作を行っている最中には、他のスレッドが同じ辞書の内部構造を同時に書き換えることはありません。公式ドキュメントでもGILについて詳しく解説されています。
注意点:「単一操作」だけが安全
ただし、ここで重要な注意点があります。スレッドセーフが保証されているのは個々の操作単位であって、複数の操作を組み合わせた処理全体ではありません。
例えば、次のようなコードを考えてみましょう。
if key not in d:
d[key] = valueこの「キーの存在確認」と「値の代入」は、それぞれ単体では安全ですが、2つの操作の間に別のスレッドが割り込む可能性があります。これを競合状態(レースコンディション)と呼びます。
複合操作にはロックを使うのが確実
チェックしてから更新するような複合的な操作を行いたい場合は、threading.Lockを使って明示的に排他制御を行うのがベストプラクティスです。
import threading
lock = threading.Lock()
with lock:
if key not in d:
d[key] = valueまた、Python 3.13以降ではGILを無効化できる実験的機能も導入されており、将来的にはGILに依存しない設計が求められる場面も増えるかもしれません。
まとめ
- Pythonの辞書をはじめとする組み込み型は、単一操作レベルでスレッドセーフ
- その背景にはGILによる排他制御がある
- ただし複数操作の組み合わせまでは保証されないため、必要に応じて
threading.Lockなどを活用する
「組み込み型だから何をしても安全」と考えるのではなく、操作の粒度を意識して適切に同期を行うことが、堅牢なマルチスレッドプログラムを書くためのポイントです。
-
ガベージコレクターはすべてのPythonオブジェクトを追跡できるのか?
Pythonの2つのメモリ管理手法Pythonでは、不要になったオブジェクト(ガベージ)を回収するために、主に2つの仕組みが使われています。1つ目は参照カウントです。これはすべてのオブジェクトに適用されますが、互いを直接・間接的に参照し合う「循環参照」を持つオブジェクト群は解放できません。そこで登場するのが、いわゆるガベージコレクターです。Pythonにはgcモジュールが用意されており、追跡対象のオブジェクトの中から循環参照を検出して回収します。ただし、循環参照の一部になり得るオブジェクトだけがこのサイクリックGCの対象となります。たとえばリストは対象ですが、文字列は他のオブジェクトを一切参照
-
Pythonでタプルが不変(イミュータブル)である理由とは?
Pythonのタプルが不変(イミュータブル)である理由は、主に以下の5つのポイントにあります。 1. 順序の維持 タプルは、Pythonにおいて「順序」を表現するためのデータ構造として設計されています。例えば、データベースからタプルのリスト形式でデータを取得する場合、各タプルは取得したフィールドの順序どおりに並んでいます。不変であることで、この順序が意図せず書き換えられる心配がありません。 2. コピー効率 不変オブジェクトの場合、実体を丸ごとコピーする代わりに、エイリアス(同じオブジェクトへの参照の別名)を作成するだけで済みます。変数を参照として束縛するだけでよいため、メモリ消費と処理時