CPython のグローバル インタープリター ロック (GIL) とは何ですか?
-
18-09-2019 - |
質問
グローバル インタープリタ ロックとは何ですか?また、それが問題になるのはなぜですか?
Python から GIL を削除することに関して多くの騒ぎが立てられていますが、私はそれがなぜそれほど重要なのかを理解したいと思っています。私はコンパイラもインタプリタも自分で書いたことがないので、詳細については倹約しないでください。おそらく理解してもらう必要があるでしょう。
解決
PythonのGILは、異なるスレッドからのインタプリタ内部へのアクセスをシリアル化することを意図しています。マルチコアシステムでは、複数のスレッドが効果的に複数のコアを利用することができないことを意味します。あなたが詳細にそれを理解したい場合は、 - (それが唯一の理由は、マルチコアシステムの増加有病率の問題として提起されていますGILは、この問題には至らなかった場合は、ほとんどの人はGILを気にしないでしょう。)あなたはこのビデオにrel="noreferrer"> スライドのこのセットはを。それはあまりにも多くの情報かもしれませんが、あなたは詳細を尋ねた: - )
PythonのGILは唯一本当にCPythonと、リファレンス実装の問題であることに注意してください。 JythonのとIronPythonのはGILを持っていません。あなたがC拡張を書いている場合を除き、Pythonの開発者として、あなたは一般的にGILに遭遇しません。 C拡張ライターは、Pythonプロセス内の他のスレッドが実行する機会を得るように、その拡張子は、I / OをブロックしないときGILを解放する必要があります。
他のヒント
は、の本当にの互いのデータには手を触れないでください。複数のスレッドがあるとしこれらは、できるだけ独立して実行する必要があります。関数を呼び出す(例えば)のために、あなたが取得する必要がある「グローバルロック」を持っている場合は、それがボトルネックと終わることができます。あなたが最初の場所で複数のスレッドを持っていることから多くの利益を得ていない羽目になることができます。
現実世界のアナロジーにそれを置くために:100の開発者は、単一のコーヒーマグを持つ会社で働いて想像してみてください。開発者の大半は、コーヒーを待っている代わりのコーディング自分の時間を費やすだろう。
これのどれもPythonの固有ではありません - 私はPythonが最初の場所でのためにGILを必要なものの詳細を知りません。しかし、うまくいけば、それはあなたの一般的な概念のより良いアイデアを与えています。
の最初のpython GILが提供するものを理解してみましょう
任意の操作/指示をインタプリタで実行されます。 GILは、インタプリタが時間のの特定の瞬間するに単一のスレッドによって保持されることを保証します。そして、複数のスレッドを使用してPythonプログラムは、単一のインタプリタで動作します。時間の特定の瞬間に、このインタプリタは、単一のスレッドによって保持されています。これは、インタプリタを保持しているスレッドだけが時間のの任意の瞬間のでのを実行していることを意味します。
さて、なぜその問題です。
あなたのマシンが複数のコア/プロセッサを有することができます。そして、複数のコアは、すなわち複数のスレッドが時間の特定の瞬間にを実行する可能性があります。の複数のスレッドがを同時にを実行することができます。 インタプリタは、単一のスレッドによって保持されているので、彼らはコアへのアクセス権を持っているにもかかわらずしかし、他のスレッドが何もしていません。任意の時点で現在のインタプリタを保持しているスレッドによって使用されているコアであり、単一のコアは、使用されているので、だから、あなたは、複数のコアが提供するすべての利点を取得されていません。だから、あなたのプログラムは、シングルスレッドのプログラムであるかのように実行するために限りがかかります。
が、潜在的に遮断または長時間実行そのようなI / Oなどの操作、画像処理、及びnumpyの数はクランチング、GIL外起こります。ここをから撮影。だから、このような操作のために、マルチスレッド操作はまだGILの存在にもかかわらず、シングルスレッドの操作よりも速くなります。だから、GILは常にボトルネックではありません。
編集:GILはCPythonとの実装の詳細です。 IronPythonのやJythonのはGILを持っていないので、真のマルチスレッドプログラムはそれらに可能なはずです、私はこれを確認してくださいPyPyやJythonのといないのに使用したことがないと思っています。
Pythonは言葉の本当の意味でのマルチスレッドを許可していません。これは、マルチスレッドのパッケージを持っていますが、あなたのコードのアップを高速化するスレッドをマルチにしたい場合は、それはそれを使用することは通常は良い考えではありません。 Pythonはグローバルインタープリタロック(GIL)と呼ばれる構造を持っています。
https://www.youtube.com/watch?v=ph374fJqFPEする
GILは、あなたの「スレッド」の一つだけが一度に実行できることを確認します。スレッドはGILを取得し、少しの仕事は、その後、次のスレッドにGILを渡すん。これは、あなたのスレッドが並列に実行されているが、彼らは本当にちょうど同じCPUコアを使用してターンを取っているように見えるかもしれませんが、人間の目には非常に迅速にそう起こります。このすべてGILの通過は、実行にオーバーヘッドが追加されます。これは、あなたがスレッドパッケージを使用して、その後速くあなたのコードの実行を作りたい場合は、多くの場合、良いアイデアではないことを意味します。
Pythonのスレッドパッケージを使用するには理由があります。あなたは、同時にいくつかのことを実行すると、効率が懸念されていない場合、それは完全に罰金と便利です。それとも、それは多くの意味を作ることができる(一部のIOのような)何かを待つ必要があるコードを実行している場合。しかし、スレッドライブラリが文句を言わないあなたは余分なCPUコアを使用してみましょう。
あなたはでした:マルチスレッドは、(マルチ処理を行うことによって)、オペレーティング・システムへのあなたのPythonコード(例えば、スパークやHadoopの)を呼び出して、いくつかの外部アプリケーション、またはいくつかのコードあなたのPythonコードの呼び出し(例えば外部委託することができますあなたのPythonコード)は、高価なマルチスレッドのものを行うC関数を呼び出すています。
あなたは問題を抱えています。 例えば、C ++では、問題を回避する方法ですが、同時にオブジェクトのセッターを入力し、言わせます。
に2つのスレッドを防ぐために、いくつかのミューテックスロックを定義することですマルチスレッドは、Pythonで可能であるが、2つのスレッドが同時に実行することができません 1つのPythonの命令よりも細かい粒度で。 実行中のスレッドはGILと呼ばれるグローバルロックを取得されます。
これは、あなたのマルチコアプロセッサを活用するために、いくつかのマルチスレッドコードを記述し始める場合、あなたのパフォーマンスが向上しないことを意味します。 通常の問題を回避するには、マルチに行くから構成されます。
あなたは、例えばCで書いた方法の中にいるならば、GILを解放することが可能であることに注意してください。
GILの使用は、Pythonに最も一般的なCPythonのを含めそのインタプリタの一部に固有ではありません。 (#edited、コメントを参照してください)。
GILの問題は、Python 3000でまだ有効である。
Python 3.7 ドキュメント
また、次の引用も強調しておきたいと思います。 パイソン threading
ドキュメンテーション:
CPython 実装の詳細:CPython では、グローバル インタプリタ ロックにより、一度に 1 つのスレッドのみが Python コードを実行できます (ただし、特定のパフォーマンス指向のライブラリではこの制限を克服できる場合があります)。アプリケーションでマルチコア マシンの計算リソースを有効に活用したい場合は、次のようにすることをお勧めします。
multiprocessing
またはconcurrent.futures.ProcessPoolExecutor
. 。ただし、複数の I/O バインドされたタスクを同時に実行する場合には、スレッド化が依然として適切なモデルです。
これは、 の用語集エントリ global interpreter lock
これは、GIL が Python のスレッド並列処理が不向きであることを暗示していることを説明しています。 CPUバウンドタスク:
一度に 1 つのスレッドだけが Python バイトコードを実行することを保証するために CPython インタープリターによって使用されるメカニズム。これにより、オブジェクト モデル (dict などの重要な組み込み型を含む) が同時アクセスに対して暗黙的に安全になるため、CPython の実装が簡素化されます。インタプリタ全体をロックすると、マルチプロセッサ マシンによってもたらされる並列処理の多くが犠牲になりますが、インタプリタのマルチスレッド化が容易になります。
ただし、標準またはサードパーティの一部の拡張モジュールは、圧縮やハッシュなどの計算量の多いタスクを実行するときに GIL を解放するように設計されています。また、GIL は I/O を実行するときに常に解放されます。
「フリースレッド」インタープリター (より細かい粒度で共有データをロックするインタープリター) を作成するというこれまでの取り組みは、一般的な単一プロセッサーの場合にパフォーマンスが低下したため、成功していませんでした。このパフォーマンスの問題を克服すると、実装がさらに複雑になり、維持コストが高くなる可能性があると考えられています。
この引用は、CPython 実装の詳細として、dict とそれに伴う変数の代入もスレッドセーフであることを暗示しています。
次に、 のドキュメント multiprocessing
パッケージ と同様のインターフェイスを公開しながらプロセスを生成することで、GIL をどのように克服するかを説明します。 threading
:
multiprocessing は、スレッド モジュールと同様の API を使用してプロセスの生成をサポートするパッケージです。マルチプロセッシング パッケージはローカルとリモートの両方の同時実行性を提供し、スレッドの代わりにサブプロセスを使用することでグローバル インタープリタ ロックを効果的に回避します。このため、マルチプロセッシング モジュールにより、プログラマは特定のマシン上で複数のプロセッサを最大限に活用できるようになります。Unix と Windows の両方で動作します。
そしてその のドキュメント concurrent.futures.ProcessPoolExecutor
使用していることを説明する multiprocessing
バックエンドとして:
ProcessPoolExecutor クラスは、プロセスのプールを使用して呼び出しを非同期に実行する Executor サブクラスです。ProcessPoolExecutor はマルチプロセッシング モジュールを使用します。これにより、グローバル インタープリタ ロックを回避できますが、pickle 可能なオブジェクトのみを実行して返すことができることも意味します。
これは他の基本クラスと対比されるべきです ThreadPoolExecutor
それ プロセスの代わりにスレッドを使用する
ThreadPoolExecutor は、スレッドのプールを使用して呼び出しを非同期に実行する Executor サブクラスです。
そこから私たちは次のように結論付けます ThreadPoolExecutor
I/O バウンドのタスクにのみ適していますが、 ProcessPoolExecutor
CPU バウンドのタスクも処理できます。
次の質問は、そもそも GIL が存在する理由を尋ねます。 グローバル インタープリター ロックが必要な理由
プロセスとスレッドの実験
で マルチプロセッシングとスレッド化 Python Python でプロセスとスレッドの実験的な分析を行いました。
結果のクイックプレビュー:
Python (CPython など) が GIL を使用する理由
から http://wiki.python.org/moin/GlobalInterpreterLock
CPython では、グローバル インタープリター ロック (GIL) は、複数のネイティブ スレッドが Python バイトコードを同時に実行するのを防ぐミューテックスです。このロックが必要になるのは、主に CPython のメモリ管理がスレッドセーフではないためです。
Python からそれを削除するにはどうすればよいですか?
Lua のように、Python でも複数の VM を起動できるかもしれませんが、Python ではそれができません。他の理由があるはずです。
Numpy またはその他の Python 拡張ライブラリでは、GIL を他のスレッドに解放すると、プログラム全体の効率が向上する場合があります。
私は視覚効果のための本のマルチスレッドからの例を共有したいです。そこでここでは、古典的な死者ロック状況がある。
static void MyCallback(const Context &context){
Auto<Lock> lock(GetMyMutexFromContext(context));
...
EvalMyPythonString(str); //A function that takes the GIL
...
}
これでデッドロックが結果のシーケンス内のイベントを検討してください。
╔═══╦════════════════════════════════════════╦══════════════════════════════════════╗
║ ║ Main Thread ║ Other Thread ║
╠═══╬════════════════════════════════════════╬══════════════════════════════════════╣
║ 1 ║ Python Command acquires GIL ║ Work started ║
║ 2 ║ Computation requested ║ MyCallback runs and acquires MyMutex ║
║ 3 ║ ║ MyCallback now waits for GIL ║
║ 4 ║ MyCallback runs and waits for MyMutex ║ waiting for GIL ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝