Python の GIL とは
GIL(Global Interpreter Lock)は、Python インタプリタが一度に1つのスレッドだけが Python バイトコードを実行することを保証するロック機構です。マルチスレッドプログラミングを理解する上で、GIL の存在は非常に重要です。
GIL とは何か
GIL は CPython(標準の Python 実装)に存在するグローバルなロックです。このロックにより、複数のスレッドがあっても、実際に Python コードを実行できるのは常に1つのスレッドだけです。
import threading
import time
counter = 0
def increment():
global counter
for _ in range(1000000):
counter += 1
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
start = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
print(f"結果: {counter}") # 2000000 にならないことがある
print(f"時間: {time.time() - start:.2f}秒")なぜ GIL が存在するのか
GIL は Python のメモリ管理を単純化するために導入されました。
Python はオブジェクトの参照カウントでメモリを管理している。GIL がないと、複数スレッドが同時に参照カウントを変更してメモリ破損が起きる可能性がある。
多くの C 拡張モジュールは GIL を前提に書かれている。GIL があることで、スレッドセーフでない C コードも安全に呼び出せる。
GIL の影響
複数スレッドでも並列実行されない。マルチスレッドの恩恵を受けにくい。
I/O 待ち中は GIL が解放される。マルチスレッドの恩恵を受けられる。
CPU バウンドな処理では、マルチスレッドにしても速くならないことがあります。
import threading
import time
def cpu_bound():
total = 0
for i in range(10000000):
total += i
return total
# シングルスレッド
start = time.time()
cpu_bound()
cpu_bound()
print(f"シングルスレッド: {time.time() - start:.2f}秒")
# マルチスレッド
start = time.time()
t1 = threading.Thread(target=cpu_bound)
t2 = threading.Thread(target=cpu_bound)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"マルチスレッド: {time.time() - start:.2f}秒")
# ほぼ同じか、むしろ遅いことがあるI/O 処理では GIL が解放される
ファイル I/O やネットワーク通信などの I/O 操作中は、GIL が一時的に解放されます。そのため、I/O バウンドな処理ではマルチスレッドが効果的です。
import threading
import time
def io_bound():
time.sleep(1) # I/O をシミュレート
# シングルスレッド
start = time.time()
io_bound()
io_bound()
print(f"シングルスレッド: {time.time() - start:.2f}秒") # 約2秒
# マルチスレッド
start = time.time()
t1 = threading.Thread(target=io_bound)
t2 = threading.Thread(target=io_bound)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"マルチスレッド: {time.time() - start:.2f}秒") # 約1秒GIL を回避する方法
CPU バウンドな処理を並列化したい場合の選択肢があります。
プロセスごとに独立した GIL を持つため、真の並列実行が可能。
NumPy などの C 拡張は、計算中に GIL を解放することがある。
Jython や IronPython には GIL がない。
Python でマルチスレッドを使う際は、GIL の特性を理解して、I/O バウンドな処理に活用するのが効果的です。












