主演に値する公開アカウント「PythonCat」に注目
静止画|「唐坊湖スポット秋の香り」
著者|ミゲルグリンバーグ
ソース|アーキテクチャの見出し
この記事では、非同期とは何か、通常のPythonコードとの違いについて詳しく説明します。
非同期Pythonコードは「通常の(または同期)Pythonコードよりも高速である」と人々が言うのを聞いたことがありますか?それは本当ですか?
1 「同期」と「非同期」とはどういう意味ですか?
Webアプリケーションは通常、さまざまなクライアントからの多くの要求を短期間で処理します。処理の遅延を回避するには、複数のリクエストを並行して処理することを検討する必要があります。これは、「同時実行」と呼ばれることがよくあります。
この記事では、引き続きWebアプリケーションを例として使用しますが、並行性の恩恵を受ける他のタイプのアプリケーションもあります。したがって、この説明はWebアプリケーションだけを対象としたものではありません。
「同期」および「非同期」という用語は、同時アプリケーションを作成する2つの方法を指します。いわゆる「同期」サーバーは、基盤となるオペレーティングシステムでサポートされているスレッドとプロセスを使用して、この同時実行性を実現します。以下は、同期展開の概略図です。
この場合、5つのクライアントがあり、すべてがアプリケーションにリクエストを送信します。このアプリケーションのアクセスポイントはWebサーバーであり、サーバーワーカープールにサービスを割り当てることで[負荷分散](https://cloud.tencent.com/product/clb?from=10680)デバイスとして機能します。これらのワーカーは、プロセス、スレッド、または両方の組み合わせとして実装できます。これらのワーカーは、ロードバランサーによって割り当てられた要求を実行します。 Webアプリケーションフレームワーク(FlaskやDjangoなど)を使用して作成したアプリケーションロジックは、これらのワーカーで実行されます。
このタイプのソリューションは、ワーカーの数をCPUの数に設定できるため、複数のCPUを備えたサーバーに適しています。これにより、グローバルインタープリターにより、単一のPythonプロセスがロックされている間、プロセッサコアを均等に使用できます。 (GIL)制限はこれを達成できません。
欠点として、上記の概略図は、このスキームの主な制限も明確に示しています。クライアントは5人ですが、ワーカーは4人だけです。これらの5つのクライアントが同時にリクエストを送信する場合、ロードバランサーは特定のクライアントを除くすべてのリクエストをワーカープールに送信し、残りのリクエストはワーカーが変更されるのを待つためにキューに保持する必要があります利用可能である必要があります。したがって、リクエストの5分の4はすぐに応答され、残りの5分の1はしばらく待つ必要があります。サーバー最適化の鍵は、適切な数のワーカーを選択して、特定の予想される負荷に対する要求のブロックを防止または最小化することです。
非同期サーバーの構成を描くのは難しいですが、私は最善を尽くしました:
このタイプのサーバーは、ループによって制御される単一のプロセスで実行されます。このループは非常に効率的なタスクマネージャーおよびスケジューラーであり、クライアントから送信された要求を実行するタスクを作成します。長期間有効なサーバーワーカーとは異なり、非同期タスクは特定のリクエストを処理するためにループによって作成されます。そのリクエストが完了すると、タスクも破棄されます。非同期サーバーにはいつでも数百または数千のアクティブなタスクがあり、それらはすべてループの管理下で作業を実行します。
非同期タスク間の並列処理がどのように実現されるのか疑問に思われるかもしれません。これは興味深い部分です。非同期アプリケーションは、協調的なマルチタスクによってのみこれを実現するからです。これは何を意味するのでしょうか?タスクが外部イベント(データベースサーバーからの応答など)を待機する必要がある場合、タスクは同期ワーカーのように待機しませんが、待機する必要があるものをループに通知してから、制御を戻します。このタスクがデータベースによってブロックされている場合、ループは別の準備完了タスクを見つけることができます。最終的に、データベースは応答を送信し、ループは最初のタスクを再度実行する準備ができていると見なし、できるだけ早く再開します。
非同期タスクが実行を一時停止および再開する機能は、抽象的に理解するのが難しい場合があります。すでに知っていることを適用しやすくするために、Pythonで await
または yield
キーワードを使用してこれを実現することを検討できますが、非同期タスクを実装する方法はこれだけではないことが後でわかります。
非同期アプリケーションは完全に単一のプロセスまたはスレッドで実行されますが、これは驚くべきことと言えます。もちろん、このタイプの同時実行性はいくつかのルールに従う必要があるため、タスクがCPUを長時間占有することはできません。そうしないと、残りのタスクがブロックされます。非同期で実行するには、すべてのタスクを定期的にアクティブに一時停止し、制御をループに戻す必要があります。非同期アプローチの恩恵を受けるには、アプリケーションには、I / Oによってブロックされることが多く、CPUの作業が少ないタスクが必要です。 Webアプリケーションは通常、特に多数のクライアント要求を処理する必要がある場合に非常に適しています。
非同期サーバーを使用する場合、複数のCPUを最大限に活用するには、通常、次の図に示すように、ハイブリッドソリューションを作成し、ロードバランサーを追加して、各CPUで非同期サーバーを実行する必要があります。
2 Pythonで非同期を実現する2つの方法
確かに、Pythonで非同期アプリケーションを作成するには、asyncioパッケージを使用できます。このパッケージは、すべての非同期アプリケーションに必要な一時停止機能と再開機能を、コルーチンに基づいて実装します。その中で、 yield
キーワード、および更新された async
と await
は、非同期機能を構築するための asyncio
の基礎です。
https://docs.python.org/3/library/asyncio.html
TrioやCurioなど、Pythonエコシステムのコルーチンに基づく非同期ソリューションは他にもあります。 Twistedもあります。これは、すべての通常のフレームワークの中で最も古く、 asyncio
よりも早く登場しました。
非同期Webアプリケーションの作成に興味がある場合は、aiohttp、sanic、FastAPI、Tornadoなど、選択できるコルチンに基づく非同期フレームワークが多数あります。
多くの人が知らないのは、コルーチンはPythonで非同期コードを作成する2つの方法のうちの1つにすぎないということです。 2番目の方法は、pipを使用してインストールできるgreenletというライブラリに基づいています。グリーンレットは、Python関数が実行を一時停止して後で再開できるという点でコルティンに似ていますが、これを実現する方法は完全に異なります。つまり、Pythonの非同期エコシステムは2つのカテゴリに分けられます。
非同期開発のコルーチンとグリーンレットの最も興味深い違いは、前者はPython言語固有のキーワードと機能を必要とするのに対し、後者は必要としないことです。つまり、コルーチンベースのアプリケーションは特定の構文を使用して作成する必要がありますが、グリーンレットベースのアプリケーションは通常のPythonコードとほとんど同じように見えます。場合によっては、同期コードを非同期で実行できるため、これは非常に便利です。これは、 asyncio
などの通常のソリューションでは不可能です。
では、グリーンレットに関して、どのライブラリが asyncio
に相当しますか?私は3つのグリーンレットベースの非同期パッケージを知っています:Gevent、Eventlet、Meinheldですが、最後のパッケージは一般的な非同期ライブラリというよりもWebサーバーに似ています。それらはすべて独自の非同期ループ実装を持っており、ネットワークやスレッド機能を実行するものなど、Python標準ライブラリのブロッキング関数を置き換え、グリーンレットに基づいて同等のものを実装する、興味深い「モンキーパッチ」関数を提供します。ノンブロッキングバージョン。非同期で実行したい同期コードがある場合は、これらのパッケージが役立ちます。
私の知る限り、グリーンレットを明示的にサポートするWebフレームワークはFlaskだけです。このフレームワークは自動的に監視します。グリーンレットWebサーバーで実行する場合は、構成なしでそれに応じて調整されます。これを行うときは、ブロッキング関数を呼び出さないように注意する必要があります。または、ブロッキング関数を呼び出したい場合は、モンキーパッチを使用してそれらのブロッキング関数を「修正」するのが最善です。
ただし、グリーンレットの恩恵を受けるフレームワークはフラスコだけではありません。 DjangoやBottleなどの他のWebフレームワーク]にはグリーンレットがありませんが、グリーンレットWebサーバーを組み合わせ、monkey-patchingを使用してブロッキング機能を修正することにより、非同期で実行することもできます。
3 非同期は同期よりも高速ですか?
同期アプリケーションと非同期アプリケーションのパフォーマンスについては、広く誤解されています。非同期アプリケーションは、同期アプリケーションよりもはるかに高速です。
この点で、私は明確にする必要があります。同期的に記述されているか非同期的に記述されているかにかかわらず、Pythonコードはほぼ同じ速度で実行されます。コードに加えて、同時アプリケーションのパフォーマンスに影響を与える可能性のある2つの要因があります。コンテキストの切り替えとスケーラビリティです。
コンテキストスイッチ
コンテキストスイッチングと呼ばれる、実行中のすべてのタスク間でCPUに必要な作業を公平に共有すると、アプリケーションのパフォーマンスに影響を与える可能性があります。同期アプリケーションの場合、この作業はオペレーティングシステムによって実行され、基本的には構成や微調整オプションのないブラックボックスです。非同期アプリケーションの場合、コンテキストの切り替えはループによって行われます。
デフォルトのループ実装は asyncio
によって提供されますが、これはPythonで記述されており、あまり効率的ではありません。 uvloopパッケージは、代替のループスキームを提供します。その一部は、パフォーマンスを向上させるためにCで記述されています。 GeventとMeinheldが使用するイベントループもCで記述されています。 Eventletは、Pythonで記述されたループを使用します。
高度に最適化された非同期ループは、コンテキストスイッチングの点でオペレーティングシステムよりも効率的ですが、私の経験では、実際の効率の向上を確認するには、非常に大量の同時実行を実行する必要があります。ほとんどのアプリケーションでは、同期コンテキスト切り替えと非同期コンテキスト切り替えの間のパフォーマンスのギャップはそれほど明白ではないと思います。
スケーラビリティ
非同期の方が速いという神話の源は、非同期アプリケーションは一般にCPUをより効率的に使用し、より適切にスケーリングでき、同期よりも柔軟にスケーリングできるということだと思います。
上の図の同期サーバーが同時に100個の要求を受信した場合は、どうなるか考えてみてください。このサーバーは同時に最大4つのリクエストしか処理できないため、ほとんどのリクエストはキューに残り、ワーカーが割り当てられるまで待機します。
対照的に、非同期サーバーはすぐに100個のタスクを作成します(または混合モードでは、4つの非同期ワーカーのそれぞれに25個のタスクを作成します)。非同期サーバーを使用すると、すべての要求が待機せずにすぐに処理されます(ただし、このソリューションには、アクティブなデータベース接続の制限など、速度を低下させる他のボトルネックもあると言っても過言ではありません)。
これらの100個のタスクが主にCPUを使用する場合、同期スキームと非同期スキームのパフォーマンスは同じになります。これは、各CPUが固定速度で実行され、Pythonがコードを実行する速度が常に同じであり、アプリケーションによって実行される作業も同じであるためです。 。ただし、これらのタスクで多くのI / O操作が必要な場合、同期サーバーは4つの同時要求しか処理できず、高いCPU使用率を達成できません。一方、非同期サーバーは、これら100の要求すべてを並行して実行するため、CPUをビジー状態に保つのに適しています。
2つのサーバーが同じ同時実行機能を持つように、100の同時ワーカーを実行できないのはなぜか疑問に思われるかもしれません。各ワーカーには、独自のPythonインタープリターとそれに関連するすべてのリソースに加えて、アプリケーションとそのリソースの個別のコピーが必要であることに注意してください。サーバーとアプリケーションのサイズによって、実行できるワーカーインスタンスの数が決まりますが、通常、この数はそれほど大きくありません。一方、非同期タスクは非常に軽量で、単一のワーカープロセスのコンテキストで実行されるため、明らかな利点があります。
要約すると、次のシナリオでのみ、非同期の方が同期よりも高速である可能性があります。
4 結論として
この記事が非同期コードに関するいくつかの混乱と誤解に答えることができることを願っています。次の2つの重要なポイントを覚えておいてください。
非同期システムがどのように機能するかについて詳しく知りたい場合は、YouTubeのPyConで私の講演「完全な初心者のための非同期Python」をチェックしてください。 https://www.youtube.com/watch?v=iG6fr81xHKA
元のリンク:https://blog.miguelgrinberg.com/post/sync-vs-async-python-what-is-the-difference
Recommended Posts