Pythonマルチスレッド(一):GIL

2000 ワード

最近Pythonのマルチスレッドプログラミングを勉強して、いくつかの文章を書いて記録します.
GILはGlobal Interpreter Lock、すなわちグローバル解釈ロックの略であり、同じ時刻に1つのスレッドだけが1つのCPU上でバイトコードを実行し、複数のスレッドを複数のCPUにマッピングできないことを保証している.これはCPython解釈器の欠陥であり、CPythonはほとんどの環境でデフォルトのPython実行環境であり、多くのライブラリがCPythonに基づいて作成されているため、GILをPythonの問題にまとめる人が多い.
GILはスレッドのセキュリティを保護するように設計されており,マルチスレッド共有変数のため,スレッド同期がうまくいかなければ,マルチスレッドはスレッドを混乱させやすい.実際にGILがあっても、この問題は完全に解決できません.GILは実際に解放され、あるスレッドの実行が完了した後に解放されるのではなく、コードのバイトコードやタイムスライスに基づいて解放されるので、次の例です.
import threading

total = 0
def add():
    global total
    for i in range(1000000):
        total += 1

def desc():
    global total
    for i in range(1000000):
        total -= 1

thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

print(total)

このプログラムは直感的に見ると、totalに100000を加えて100000を減らし、どのスレッドが先に実行されても、最後の結果は0であるはずですが、上記のコードを何度も許可すると、コードの結果が毎回異なり、プラスとマイナスがあることがわかります.その理由はGILの解放にかかわる.まず、一般的な加算関数のバイトコードを確認します.
import dis
def add1(a):
    a += 1
    return a
print(dis.dis(add1))

結果は次のとおりです.
  2           0 LOAD_FAST                0 (a)
              2 LOAD_CONST               1 (1)
              4 INPLACE_ADD
              6 STORE_FAST               0 (a)

  3           8 LOAD_FAST                0 (a)
             10 RETURN_VALUE
None
a += 1の実行過程は、変数aをCPUにロードし、定数1をCPUにロードした後、加算操作を実行し、最後にaをメモリに格納することである.GILはPythonコードセグメントに従って解放されるのではなく、バイトコードやタイムスライスに従って解放されるため、前の例では、add関数が加算後もメモリに保存されていなければ、GIL解放、desc関数が実行権を獲得し、このときロード時にロードする変数totalが加算操作されていないtotalである、従って,従来のaddに相当する関数は機能せず,複数回のループを行った後,プログラムの実行結果は自然に0ではない.このことを競合条件(race condition)と呼び,GILがなくてもこのような問題が発生する.解決策はロックメカニズムを用いることであり,後述する.
もう1つの条件は、GIL解放をもたらす.それは、プログラムがIOオペレーションに遭遇し、time.sleepがプログラムをブロックすると、マルチスレッドがIOオペレーションの問題を処理するのに非常に有効である.