11.1 Global Interpreter Lock
Global Interpreter Lock (GIL) は、CPythonインタプリタ内の メカニズムで、任意の時点で1つのスレッドのみが Pythonバイトコードを実行できるようにしています。 GIL はスレッドの並列実行を防ぐため、 特にマルチコアプロセッサでの マルチスレッドプログラムのパフォーマンスに 悪影響を及ぼすことがあります。
GILの存在理由
メモリ管理の簡素化:GILは メモリ管理とガベージコレクションを簡略化し、 Pythonの実装を簡単にしています。
スレッドセーフティ:GILは レースコンディションを防ぎ、コードの実行を ロックなしでスレッドセーフにします。
GILによる問題
制約されたパフォーマンス:計算集約的な タスクを実行するマルチスレッドプログラムは、 GILの制約により、 マルチコアプロセッサの利点を 十分に活用できません。
パフォーマンスの歪み:タスクの実行に スレッドを集中的に使用するプログラムは、 スレッド間のコンテキストスイッチングによって パフォーマンス低下を経験するかもしれません。
現在、「GILの回避」には4つの主要な方法があります:
11.2 マルチプロセッシングの使用 (multiprocessing)
multiprocessingモジュールは、 パラレルに実行され、GILに 制約されないプロセスを作成することができます。 各プロセスは独自のPythonインタプリタとメモリを 持つからです。
例:
import multiprocessing
def worker(num):
print(f'Worker: {num}')
def main():
processes = []
for i in range(5):
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
for p in processes:
p.join()
main()
11.3 非同期プログラミング
asyncioを使用した非同期プログラミングは、 メインスレッドをブロックすることなく、タスクを 並列に実行することが可能です。 ただし、直接GILを回避するわけではありませんが、 待機時間を効率的に利用して他のタスクを実行できるようにします。
例:
import asyncio
async def main():
await asyncio.sleep(1)
print('Hello')
asyncio.run(main())
11.4 独自のスレッド管理を行うライブラリの使用
NumPyやSciPyのような一部のライブラリは、 Cで書かれていて、独自のスレッド管理メカニズムを使用して GILを回避し、計算を効率的に マルチコアプロセッサで実行できます。
実は、これはPythonが遅いにもかかわらず成功した 主な理由の一つです。すべての複雑な計算は C/C++で書き直され、すべてのプロセッサコア、 あるいは場合によってはGPUのコアでも実行されています。 現代のGPUには何千ものコアがあります。
したがって、言語がどれほど速いか遅いかは もはや重要ではありません。すべてのリソースを 消費する計算は、外部ライブラリで実行されるか、 リモートのデータセンターにオフロードされるからです。
11.5 Pythonインタプリタ外での計算の実行
C/C++などの他の言語で拡張機能を使用することで、 Pythonインタプリタの外で計算を実行し、その結果を 返すことができます。これにより、計算集約的な 処理を行っている間、GILによる ロックを回避することができます。
Cythonの使用例:
# example.pyx
def compute(int n):
cdef int i, result = 0
for i in range(n):
result += i
return result
コンパイルおよび使用:
cythonize -i example.pyx
import example
print(example.compute(1000000))
GO TO FULL VERSION