Python中GILの使用詳細

3213 ワード

1、GIL概要
GILのフルネームはGlobal Interpreter Lockといい、大域分解器錠です。
1.1 GIL設計理念と制限
pythonのコードはpython仮想マシン(解釈器の主循環、CPythonバージョンともいう)によって制御され、pythonは設計の当初から解釈器の主循環の中で一つのスレッドだけが動作していると考えられていた。つまり任意の時間に1つのスレッドだけがインタプリタで動作します。python仮想マシンへのアクセス制御は、グローバル解釈でGIL制御されており、このロックこそが同じ時刻を制御しているのです。
外部コード(C、C+++拡張関数など)を呼び出すと、GILはこの関数が終了するまでロックされます。
pythonではオペレーティングシステムレベルのスレッドを使用していますが、linuxではpthread、windowでは元のスレッドを使用しています。
上記の概説からは、pyは同じ時間に1スレッドしか走れないことが分かります。このようにマルチスレッドを走る場合は、スレッドが大域解释器のロックを取得してからしか実行できません。大域解释器のロックは1つしかないので、マルチコアの場合でもシングルコアの機能を発揮することができます。
このようにpyは力をあげないように見えます。GILは直接にCPythoonが物理的に多核の性能を利用して運行を加速できなくなります。なぜこのようなデザインがありますか?Gido van Rossumがpythonを創造する時を考慮して、前世紀90年代、多核cpuは完全に想像できないで、今ハードウエアの発展速度が速すぎるため、プログラムの編纂はcpuの全部の性能を使い尽くして、さもなくば淘汰されて、pythonに対して同様にこのようにします。
上で主に言っているのはこのデザインの劣勢です。次にその強みを検討します。
GILの設計はCPythonの実現を簡略化しており、対象モデルを含み、重要な内装タイプは辞書のように、すべて暗黙的に同時にアクセスできるようにしている。グローバル・インタプリタをロックすることにより、マルチスレッドのサポートが容易に実現されるが、マルチプロセッサホストの並列計算能力も損なわれる。
しかし、標準であれ、第三者の拡張モジュールであれ、密集計算タスクを行う際にGILを解放するように設計されています。また、IO操作では、GILはいつもリリースされます。内部に建設されたオペレーティングシステムCコードに直面する全てのプログラムに対して、GILはこのIO呼び出し前にリリースされ、他のスレッドがこのIOを待っている間に実行できるようにする。純計算のプログラムであれば、IO操作がなく、解釈器は100回または15 msごとにGILを解放する。
ここでは、IO密集型のpythonは、密集型を計算するプログラムよりもマルチスレッド環境を利用するのに便利であると理解できる。
1.2 GILのスレッド実行に対する影響
マルチスレッド環境では、python仮想マシンは以下のように実行される。
  • GIL
  • を設定します。
  • は、スレッドに切り替えて
  • を実行する。
  • はコードを実行しています。ここには2つのメカニズムがあります。
  • 指定数のバイトコード命令(100個)
  • 固定時間15 msスレッド自発的に制御
  • を譲る。
  • はスレッドを睡眠状態に設定します。
  • ロック解除GIL
  • は、上記ステップ
  • を再び繰り返す。
    前節ではpython言語はプログラムと同じように、cpuの性能を使い果たしたいと考えていますが、pyの対応方法を検討しています。
    pythonの対処方法は簡単です。新しいpython 3の中にGILがあります。その原因はいくつかありますか?
  • CPythonのGILは本来、全大域の解釈器と環境状態変数を保護するために用いられています。GILを除去するなら、より多くのより細かい錠が必要です。あるいはLock-Freeアルゴリズムを採用します。どちらを採用しても、マルチスレッドの安全を確保するには、一つのGILを維持するよりもずっと難しいです。また、変更されたのはCPythonのコードツリーであり、様々な第三者拡張もGILに依存しています。
  • はさらに、ある人はテストをしてGILを取り除いて、もっと細かい粒度の錠を加えます。しかし,実際の検出は,単一スレッドに対して,性能はより低い。利用する物理のcpuだけが一定数になると、GILバージョンより性能が優れます。今はほとんどのpythonプログラムがシングルスレッドです。
  • そして一番重要なのは以下のいくつかの方面にあります。簡単に言えばpyは変えないで、同じように私達の需要を実現できます。
  • は2.6から引き出すマルチプロセス標準ライブラリmuttil processingで、マルチプロセスのpython編纂をマルチスレッドのような程度に簡略化し、GILによる多くの不利益を大幅に軽減します。
  • は、ctypesを利用してGIL:ctypeesをバイパスすることで、pyを任意のCダイナミックライブラリの導出関数に直接呼び出すことができます。作るのはctypesでpythonコードを書けばいいです。また、ctypesはC関数を呼び出す前にGILをリリースします。
  • pythonでGILは同じタイミングで一つのスレッドだけを一つのcpuに実行させ、複数のスレッドを複数のcpuにマッピングして実行することはできませんが、GILはずっと占有していません。適切な時にリリースされます。
    
    import threading
    count = 0
    def add():
      global count
      for i in range(10**6):
        count += 1
    
    def minus():
      global count
      for i in range(10**6):
        count -= 1
    
    thread1 = threading.Thread(target=add)
    thread2 = threading.Thread(target=minus)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(count)
    3回ずつ実行した結果:
    -59452
    60868
    -77007
    countは固定値ではなく、GILはある時点で釈放されるということですが、GILは具体的にどのような状況で釈放されますか?
    1.実行するバイトコードの行数が一定の閾値に達する
    2.時間スライスによって分割し、一定の時間閾値に到達する
    3.IO操作に遭遇した場合、自発的に釈放する。
    以上はこの文章の全部の内容です。本文の内容は皆さんの学習や仕事に対して一定の参考となる学習価値を持っています。質問があれば、メッセージを書いて交流してください。ありがとうございます。