ホームページ >バックエンド開発 >Python チュートリアル >Gunicorn と Python GIL を 1 つの記事で理解する

Gunicorn と Python GIL を 1 つの記事で理解する

WBOY
WBOY転載
2023-04-12 10:40:101292ブラウズ

Gunicorn と Python GIL を 1 つの記事で理解する

Python GIL とは何か、その仕組み、Gunicorn への影響。

本番環境ではどの Gunicorn ワーカー タイプを選択すればよいですか?

Python には、1 つのスレッドの実行 (つまり、バイトコードの解釈) のみを許可するグローバル ロック (GIL) があります。私の意見では、Python サービスを最適化したい場合は、Python が同時実行性をどのように処理するかを理解することが不可欠です。

Python と gunicorn では同時実行を処理するためのさまざまな方法が提供されますが、すべてのユースケースをカバーする特効薬はないため、オプション、トレードオフ、および各オプションの利点を理解することをお勧めします。

Gunicorn ワーカー タイプ

Gunicorn は、「ワーカー タイプ」の概念の下でこれらのさまざまなオプションを公開します。各タイプは、特定の一連のユースケースに適しています。

    #sync
  • ——プロセスを並行して実行される N 個のプロセスにフォークしてリクエストを処理します。
  • gthread
  • ——N 個のスレッドを生成して、リクエストを同時に処理します。
  • eventlet/gevent - 同時リクエストを処理するために緑色のスレッドを生成します。
Gunicorn 同期ワーカー

これは、リクエストを並行して処理する N プロセスをフォークすることだけが同時実行オプションである最も単純なタイプのジョブです。

これらはうまく機能しますが、多くのオーバーヘッド (メモリや CPU コンテキストの切り替えなど) が発生し、リクエスト時間のほとんどが I/O 待機にかかる場合、セックスのスケーリングが必要になります。悪い。

Gunicorn gthread worker

gthread Worker は、プロセスごとに N 個のスレッドを作成できるようにすることで、これを改善しています。これにより、コードのより多くのインスタンスを同時に実行できるため、I/O パフォーマンスが向上します。これは、GIL の影響を受ける 4 つのうちの唯一の 1 つです。

Gunicorn イベントレットおよび gevent ワーカー

eventlet/gevent ワーカーは、軽量のユーザー スレッド (別名グリーン スレッド、グリーンレットなど) を実行することで、gthread モデルをさらに改善しようとします。

#これにより、システム スレッドと比較して非常に少ないコストで数千のグリーンレットを作成できるようになります。 もう 1 つの違いは、プリエンプティブな作業モデルではなく共同作業モデルに従っており、ブロックされるまで作業が中断されないことです。まず、リクエストを処理するときの gthread ワーカー スレッドの動作と、それが GIL によってどのような影響を受けるかを分析します。

各リクエストが 1 つのプロセスによって直接処理される同期とは異なり、gthread では、複数のプロセスにコストを発生させることなく、各プロセスに N 個のスレッドがあり、より適切に拡張できます。同じプロセス内で複数のスレッドを実行しているため、GIL によってそれらのスレッドが並行して実行されなくなります。

GIL はプロセスまたは特別なスレッドではありません。これは単なるブール変数であり、そのアクセスはミューテックスによって保護されており、各プロセス内で 1 つのスレッドのみが実行されることが保証されます。それがどのように機能するかは上の写真で見ることができます。この例では、2 つのシステム スレッドが同時に実行されており、各スレッドが 1 つのリクエストを処理していることがわかります。プロセスは次のようになります。

    スレッド A は GIL を保持し、リクエストの処理を開始します。
  • しばらくすると、スレッド B がリクエストを処理しようとしますが、GIL を保持できません。
  • B タイムアウトに達する前に GIL が解放されない場合は、タイムアウトを設定して強制的に GIL を解放します。
  • #A GIL はタイムアウトに達するまで解放されません。
  • B は、gil_drop_request フラグを設定して、A に GIL を直ちに解放するよう強制します。
  • A は、GIL を解放し、別のスレッドが GIL を取得するまで待機します。これは、他のスレッドが GIL を取得できないまま A が GIL の解放と取得を続ける状況を回避します。
  • B 走り始めます。
  • B I/O をブロックしながら GIL を解放します。
  • #A が実行を開始します。
  • B 再度実行しようとしましたが、中断されました。
  • A はタイムアウトに達する前に完了します。
  • B 実行が完了しました。
  • 同じシナリオですが、gevent を使用します

Gunicorn と Python GIL を 1 つの記事で理解するプロセスを使用せずに同時実行性を高めるもう 1 つのオプションは、グリーンレットを使用することです。このワーカーは、同時実行性を高めるために、「システム スレッド」の代わりに「ユーザー スレッド」を生成します。

これは、それらが GIL の影響を受けないことを意味しますが、CPU によって並列にスケジュールできないため、依然として並列処理を増やすことができないことも意味します。

  • Greenlet A は、I/O イベントが発生するか実行が完了するまで実行を開始します。
  • グリーンレット B は、グリーンレット A がイベント ループを解放するまで待機します。
  • #Aは終わりました。
  • Bスタート。
  • B はイベント ループを解放して I/O を待機します。
  • Bが完了しました。

このケースでは、グリーンレット タイプのワーカーを雇うことが理想的ではないことは明らかです。結局、2 番目のリクエストは最初のリクエストが完了するまで待機し、その後再び I/O を待機することになります。

これらのシナリオでは、コンテキストの切り替えに時間を無駄にせず、複数のシステム スレッドを実行するオーバーヘッドを回避できるため、greenlet コラボレーション モデルが真価を発揮します。

この記事の最後にあるベンチマーク テストでこれを確認します。 ここで次の疑問が生じます:

    スレッド コンテキスト スイッチのタイムアウトを変更すると、サービスの遅延とスループットに影響しますか?
  • I/O と CPU の作業を混在させる場合に、gevent/eventlet と gthread のどちらを選択するかを選択する方法。
  • gthread ワーカーを使用してスレッド数を選択する方法。
  • GIL を回避するには、同期ワーカーを使用してフォークされたプロセスの数を増やすだけでよいでしょうか?
これらの質問に答えるには、監視して必要なメトリクスを収集し、それらの同じメトリクスに対して調整されたベンチマークを実行する必要があります。実際の使用パターンとの相関関係がゼロの合成ベンチマークを実行しても意味がありません。以下のグラフは、さまざまなシナリオのレイテンシーとスループットのメトリクスを示しており、すべてがどのように連携して機能するかを示しています。

GIL 切り替え間隔のベンチマーク

ここでは、GIL スレッド切り替え間隔/タイムアウトの変更がリクエストのレイテンシーにどのような影響を与えるかを確認できます。予想通り、スイッチング間隔が短くなるにつれて IO レイテンシーは改善されます。これは、CPU バウンドのスレッドがより頻繁に GIL を解放し、他のスレッドが作業を完了できるようにするために発生します。 Gunicorn と Python GIL を 1 つの記事で理解する

しかし、これは万能薬ではありません。切り替え間隔を短縮すると、CPU バウンドのスレッドが完了するまでにかかる時間が長くなります。また、スレッドの定期的な切り替えによるオーバーヘッドの増加により、全体的なレイテンシが増加し、タイムアウトが減少していることもわかります。自分で試してみたい場合は、次のコードを使用して切り替え間隔を変更できます。

Gunicorn と Python GIL を 1 つの記事で理解する

CPU バウンド リクエストを使用した gthread と gevent レイテンシのベンチマーク

Gunicorn と Python GIL を 1 つの記事で理解する

全体として、ベンチマークは、GIL バインドされたスレッドとグリーンレットがどのように機能するかに関する以前の分析からの直感を反映していることがわかります。

Gthread では、切り替え間隔によって長時間実行されているスレッドが強制的に解放されるため、IO バウンド リクエストの平均レイテンシーが向上します。

gevent CPU バウンドのリクエストは、他のリクエストの処理が中断されないため、gthread よりも待ち時間が長くなります。

CPU バウンド リクエストを使用した gthread と gevent のスループットのベンチマーク

Gunicorn と Python GIL を 1 つの記事で理解する

ここでの結果には、gevent と gthread の以前の比較も反映されています。スループット。これらのベンチマークは、実行される作業の種類に大きく依存しており、必ずしもユースケースに直接反映されるとは限りません。

これらのベンチマークの主な目的は、リクエストに対応する各 CPU コアを最大限に活用するために何をテストし、測定すべきかについてのガイダンスを提供することです。

すべての Gunicorn ワーカーでは実行するプロセスの数を指定できるため、変更されるのは各プロセスが同時接続を処理する方法です。したがって、テストを公平にするために、必ず同じ数のワーカーを使用してください。ここで、ベンチマークから収集したデータを使用して、前の質問に答えてみましょう。

スレッド コンテキスト スイッチのタイムアウトを変更すると、サービスの遅延とスループットに影響しますか? ################## ############確かに。ただし、大部分のワークロードにとって、これはゲームチェンジャーではありません。

I/O と CPU の作業を混在させる場合、gevent/eventlet と gthread のどちらを選択すればよいでしょうか?ご覧のとおり、ghtread は、CPU をより集中的に使用する作業がある場合に、より優れた同時実行性を実現する傾向があります。 gthread ワーカーのスレッド数を選択するにはどうすればよいですか?

ベンチマークが運用環境のような動作をシミュレートできる限り、明らかにピークパフォーマンスが確認されますが、その後、スレッドが多すぎるためにパフォーマンスが低下し始めます。

GIL を回避するには、同期ワーカーを使用し、フォークされたプロセスの数を増やすだけでよいでしょうか?

I/O がほぼゼロでない限り、プロセスのみでスケーリングすることは最良の選択肢ではありません。

結論

コルーチン/グリーンレットは、スレッド間の割り込みやコンテキストの切り替えを回避するため、CPU 効率を向上させることができます。コルーチンは、スループットと引き換えにレイテンシを実現します。

IO エンドポイントと CPU バウンドのエンドポイントを混在させると、コルーチンによってさらに予測不能なレイテンシーが発生する可能性があります。CPU バウンドのエンドポイントは、他の受信リクエストの処理を中断されません。時間をかけて gunicorn を正しく設定すれば、GIL は問題になりません。

以上がGunicorn と Python GIL を 1 つの記事で理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事は51cto.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。