Python GIL とは何か、その仕組み、Gunicorn への影響。
本番環境ではどの Gunicorn ワーカー タイプを選択すればよいですか?
Python には、1 つのスレッドの実行 (つまり、バイトコードの解釈) のみを許可するグローバル ロック (GIL) があります。私の意見では、Python サービスを最適化したい場合は、Python が同時実行性をどのように処理するかを理解することが不可欠です。
Python と gunicorn では同時実行を処理するためのさまざまな方法が提供されますが、すべてのユースケースをカバーする特効薬はないため、オプション、トレードオフ、および各オプションの利点を理解することをお勧めします。
Gunicorn は、「ワーカー タイプ」の概念の下でこれらのさまざまなオプションを公開します。各タイプは、特定の一連のユースケースに適しています。
これは、リクエストを並行して処理する N プロセスをフォークすることだけが同時実行オプションである最も単純なタイプのジョブです。
これらはうまく機能しますが、多くのオーバーヘッド (メモリや CPU コンテキストの切り替えなど) が発生し、リクエスト時間のほとんどが I/O 待機にかかる場合、セックスのスケーリングが必要になります。悪い。
Gunicorn gthread workergthread Worker は、プロセスごとに N 個のスレッドを作成できるようにすることで、これを改善しています。これにより、コードのより多くのインスタンスを同時に実行できるため、I/O パフォーマンスが向上します。これは、GIL の影響を受ける 4 つのうちの唯一の 1 つです。
Gunicorn イベントレットおよび gevent ワーカーeventlet/gevent ワーカーは、軽量のユーザー スレッド (別名グリーン スレッド、グリーンレットなど) を実行することで、gthread モデルをさらに改善しようとします。
#これにより、システム スレッドと比較して非常に少ないコストで数千のグリーンレットを作成できるようになります。 もう 1 つの違いは、プリエンプティブな作業モデルではなく共同作業モデルに従っており、ブロックされるまで作業が中断されないことです。まず、リクエストを処理するときの gthread ワーカー スレッドの動作と、それが GIL によってどのような影響を受けるかを分析します。
各リクエストが 1 つのプロセスによって直接処理される同期とは異なり、gthread では、複数のプロセスにコストを発生させることなく、各プロセスに N 個のスレッドがあり、より適切に拡張できます。同じプロセス内で複数のスレッドを実行しているため、GIL によってそれらのスレッドが並行して実行されなくなります。
GIL はプロセスまたは特別なスレッドではありません。これは単なるブール変数であり、そのアクセスはミューテックスによって保護されており、各プロセス内で 1 つのスレッドのみが実行されることが保証されます。それがどのように機能するかは上の写真で見ることができます。この例では、2 つのシステム スレッドが同時に実行されており、各スレッドが 1 つのリクエストを処理していることがわかります。プロセスは次のようになります。
プロセスを使用せずに同時実行性を高めるもう 1 つのオプションは、グリーンレットを使用することです。このワーカーは、同時実行性を高めるために、「システム スレッド」の代わりに「ユーザー スレッド」を生成します。
これは、それらが GIL の影響を受けないことを意味しますが、CPU によって並列にスケジュールできないため、依然として並列処理を増やすことができないことも意味します。
このケースでは、グリーンレット タイプのワーカーを雇うことが理想的ではないことは明らかです。結局、2 番目のリクエストは最初のリクエストが完了するまで待機し、その後再び I/O を待機することになります。
これらのシナリオでは、コンテキストの切り替えに時間を無駄にせず、複数のシステム スレッドを実行するオーバーヘッドを回避できるため、greenlet コラボレーション モデルが真価を発揮します。
この記事の最後にあるベンチマーク テストでこれを確認します。 ここで次の疑問が生じます:
ここでは、GIL スレッド切り替え間隔/タイムアウトの変更がリクエストのレイテンシーにどのような影響を与えるかを確認できます。予想通り、スイッチング間隔が短くなるにつれて IO レイテンシーは改善されます。これは、CPU バウンドのスレッドがより頻繁に GIL を解放し、他のスレッドが作業を完了できるようにするために発生します。
しかし、これは万能薬ではありません。切り替え間隔を短縮すると、CPU バウンドのスレッドが完了するまでにかかる時間が長くなります。また、スレッドの定期的な切り替えによるオーバーヘッドの増加により、全体的なレイテンシが増加し、タイムアウトが減少していることもわかります。自分で試してみたい場合は、次のコードを使用して切り替え間隔を変更できます。ここでの結果には、gevent と gthread の以前の比較も反映されています。スループット。これらのベンチマークは、実行される作業の種類に大きく依存しており、必ずしもユースケースに直接反映されるとは限りません。
これらのベンチマークの主な目的は、リクエストに対応する各 CPU コアを最大限に活用するために何をテストし、測定すべきかについてのガイダンスを提供することです。
すべての Gunicorn ワーカーでは実行するプロセスの数を指定できるため、変更されるのは各プロセスが同時接続を処理する方法です。したがって、テストを公平にするために、必ず同じ数のワーカーを使用してください。ここで、ベンチマークから収集したデータを使用して、前の質問に答えてみましょう。
I/O と CPU の作業を混在させる場合、gevent/eventlet と gthread のどちらを選択すればよいでしょうか?ご覧のとおり、ghtread は、CPU をより集中的に使用する作業がある場合に、より優れた同時実行性を実現する傾向があります。 gthread ワーカーのスレッド数を選択するにはどうすればよいですか?
ベンチマークが運用環境のような動作をシミュレートできる限り、明らかにピークパフォーマンスが確認されますが、その後、スレッドが多すぎるためにパフォーマンスが低下し始めます。GIL を回避するには、同期ワーカーを使用し、フォークされたプロセスの数を増やすだけでよいでしょうか?
結論
コルーチン/グリーンレットは、スレッド間の割り込みやコンテキストの切り替えを回避するため、CPU 効率を向上させることができます。コルーチンは、スループットと引き換えにレイテンシを実現します。
IO エンドポイントと CPU バウンドのエンドポイントを混在させると、コルーチンによってさらに予測不能なレイテンシーが発生する可能性があります。CPU バウンドのエンドポイントは、他の受信リクエストの処理を中断されません。時間をかけて gunicorn を正しく設定すれば、GIL は問題になりません。
以上がGunicorn と Python GIL を 1 つの記事で理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。