グローバル インタープリター ロックとは何ですか? なぜ問題なのですか?
Python から GIL を削除することに関して多くの騒ぎがありましたが、なぜそれがそれほど重要なのかを理解したいと思います。私は自分でコンパイラやインタープリターを書いたことがないので、詳細を倹約しないでください。おそらく理解する必要があるでしょう。
グローバル インタープリター ロックとは何ですか? なぜ問題なのですか?
Python から GIL を削除することに関して多くの騒ぎがありましたが、なぜそれがそれほど重要なのかを理解したいと思います。私は自分でコンパイラやインタープリターを書いたことがないので、詳細を倹約しないでください。おそらく理解する必要があるでしょう。
Python の GIL は、異なるスレッドからインタープリター内部へのアクセスをシリアル化することを目的としています。マルチコア システムでは、複数のスレッドが複数のコアを効果的に利用できないことを意味します。(GIL がこの問題を引き起こさなかった場合、ほとんどの人は GIL を気にしないでしょう。マルチコア システムの普及が進んでいるため、問題として提起されているだけです。) 詳細を理解したい場合は、このビデオを見るか、この一連のスライドを見ることができます。情報が多すぎるかもしれませんが、詳細を尋ねました:-)
Python の GIL は、参照実装である CPython だけの問題であることに注意してください。Jython と IronPython には GIL がありません。Python 開発者は、C 拡張機能を作成していない限り、通常、GIL に出くわすことはありません。C 拡張機能の作成者は、拡張機能が I/O をブロックしているときに GIL を解放する必要があります。これにより、Python プロセス内の他のスレッドが実行される機会が得られます。
お互いのデータに実際には触れない複数のスレッドがあるとします。それらは可能な限り独立して実行する必要があります。関数を呼び出すために取得する必要がある「グローバルロック」がある場合、それがボトルネックになる可能性があります。そもそも複数のスレッドを使用してもあまりメリットが得られない場合があります。
現実世界の例えで言えば、100 人の開発者が 1 つのコーヒー マグカップしか持っていない会社で働いていると想像してください。ほとんどの開発者は、コーディングの代わりにコーヒーを待つことに時間を費やしていました。
これはいずれも Python 固有のものではありません。Python が最初に GIL を必要とした理由の詳細はわかりません。ただし、一般的な概念についてのより良いアイデアが得られることを願っています。
まず、python GIL が提供するものを理解しましょう。
操作/命令はインタプリタで実行されます。GIL は、インタプリタが特定の時点で単一のスレッドによって保持されることを保証します。そして、複数のスレッドを持つ Python プログラムは、単一のインタープリターで動作します。特定の時点で、このインタープリターは単一のスレッドによって保持されます。これは、インタプリタを保持しているスレッドだけが常に実行されていることを意味します。
なぜそれが問題なのですか:
マシンに複数のコア/プロセッサが搭載されている可能性があります。また、複数のコアを使用すると、複数のスレッドを同時に実行できます。つまり、特定の時点で複数のスレッドを実行できます。. しかし、インタープリターは単一のスレッドによって保持されているため、他のスレッドはコアにアクセスできても何もしていません。したがって、現在インタープリターを保持しているスレッドによって使用されているコアである単一のコアのみが使用されているため、複数のコアによって提供される利点は得られません。そのため、プログラムの実行には、シングル スレッド プログラムの場合と同じくらいの時間がかかります。
ただし、I/O、画像処理、NumPy 数値計算などの潜在的なブロックまたは長時間実行される操作は、GIL の外部で発生します。ここから撮影。したがって、このような操作では、GIL が存在するにもかかわらず、マルチスレッド操作はシングルスレッド操作よりも高速です。したがって、GIL が常にボトルネックになるわけではありません。
編集: GIL は CPython の実装の詳細です。IronPython と Jython には GIL がないため、真のマルチスレッド プログラムが可能であるはずです。
Python は、本当の意味でのマルチスレッドを許可していません。これにはマルチスレッド パッケージがありますが、コードを高速化するためにマルチスレッドを使用する場合は、通常、これを使用することはお勧めできません。Python には、Global Interpreter Lock (GIL) と呼ばれる構造があります。
https://www.youtube.com/watch?v=ph374fJqFPE
GIL は、一度に 1 つの「スレッド」のみが実行できるようにします。スレッドは GIL を取得し、少し作業を行ってから、GIL を次のスレッドに渡します。これは非常に迅速に行われるため、人間の目にはスレッドが並行して実行されているように見えるかもしれませんが、実際には同じ CPU コアを順番に使用しているだけです。このすべての GIL の受け渡しにより、実行にオーバーヘッドが追加されます。これは、コードをより高速に実行したい場合、threading パッケージを使用することは多くの場合良い考えではないことを意味します。
Python のスレッド化パッケージを使用する理由があります。いくつかのことを同時に実行したい場合で、効率が問題にならない場合は、まったく問題なく便利です。または、何か (IO など) を待機する必要があるコードを実行している場合は、非常に理にかなっています。ただし、スレッド ライブラリでは、追加の CPU コアを使用できません。
マルチスレッドは、オペレーティング システム (マルチプロセッシングを行うことによって)、Python コードを呼び出す外部アプリケーション (Spark や Hadoop など)、または Python コードが呼び出すコード (たとえば、Pythonコードは、高価なマルチスレッド処理を行う C 関数を呼び出します)。
2 つのスレッドが同じ変数にアクセスするたびに、問題が発生します。たとえば C++ では、この問題を回避する方法は、mutex ロックを定義して、2 つのスレッドが同時にオブジェクトのセッターに入るのを防ぐことです。
Python ではマルチスレッド化が可能ですが、1 つの Python 命令よりも細かい粒度で 2 つのスレッドを同時に実行することはできません。実行中のスレッドは、GIL と呼ばれるグローバル ロックを取得しています。
つまり、マルチコア プロセッサを利用するためにマルチスレッド コードを書き始めても、パフォーマンスは向上しません。通常の回避策は、マルチプロセスにすることです。
たとえば、C で記述したメソッド内にいる場合は、GIL を解放できることに注意してください。
GIL の使用は Python に固有のものではなく、最も一般的な CPython を含む一部のインタープリターに固有のものです。(#編集済み、コメントを参照)
GIL の問題は、Python 3000 でも有効です。
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 ║
╚═══╩════════════════════════════════════════╩══════════════════════════════════════╝