より大きなスレッドプールを使用する代わりに非同期リクエストを使用するのはなぜですか? 質問する

より大きなスレッドプールを使用する代わりに非同期リクエストを使用するのはなぜですか? 質問する

オランダのテックデイズでスティーブ・サンダーソンが次のようなプレゼンテーションを行いました。C#5、ASP.NET MVC 4、非同期Web

彼は、リクエストの完了に長い時間がかかると、スレッドプールのすべてのスレッドがビジー状態になり、新しいリクエストは待機しなければならなくなると説明しました。サーバーは負荷を処理できず、すべてが遅くなります。

次に、非同期 Web リクエストを使用すると、作業が別のスレッドに委任され、スレッドプールが新しい着信リクエストにすばやく応答できるため、パフォーマンスが向上することを説明しました。さらに、これをデモし、50 件の同時リクエストに最初は 50 * 1 秒かかりましたが、非同期動作を導入すると合計で 1.2 秒しかかからないことを示しました。

しかし、これを見た後でもまだいくつか疑問が残ります。

  1. なぜもっと大きなスレッドプールを使用できないのでしょうか? async/await を使用して別のスレッドを起動する方が、最初からスレッドプールを増やすよりも遅くなるのではないでしょうか? 実行しているサーバーに突然スレッドが増えるとか、そういうことではないのでしょうか?

  2. ユーザーからのリクエストは、非同期スレッドが終了するのをまだ待機しています。プールのスレッドが何か他の処理を行っている場合、どのようにして「UI」スレッドはビジー状態を維持するのでしょうか? Steve は「何かが終了したことを認識するスマート カーネル」について言及していました。これはどのように機能するのでしょうか?

ベストアンサー1

これは非常に良い質問であり、これを理解することが、非同期 IO がなぜそれほど重要なのかを理解する鍵となります。新しい async/await 機能が C# 5.0 に追加された理由は、非同期コードの記述を簡素化するためです。ただし、サーバーでの非同期処理のサポートは新しいものではなく、ASP.NET 2.0 から存在しています。

Steve が示したように、同期処理では、ASP.NET (および WCF) の各リクエストはスレッド プールから 1 つのスレッドを取得します。彼がデモした問題は、「スレッドプールの枯渇サーバー上で同期 IO を実行すると、スレッド プールのスレッドは IO の実行中はブロックされたままになります (何も実行されません)。スレッド プール内のスレッド数には制限があるため、負荷がかかっている場合は、スレッド プールのすべてのスレッドが IO を待機してブロックされ、要求がキューに入れられ始め、応答時間が長くなる可能性があります。すべてのスレッドが IO の完了を待機しているため、CPU 使用率は 0% に近くなります (応答時間は急激に長くなりますが)。

あなたが尋ねているのは(なぜもっと大きなスレッドプールを使用できないのでしょうか?) は非常に良い質問です。実際のところ、これまでほとんどの人がスレッド プールの枯渇の問題を解決してきたのは、スレッド プールにスレッドを増やすという方法でした。Microsoft の一部のドキュメントでは、スレッド プールの枯渇が発生する可能性がある状況の修正方法として、この方法が示されています。これは許容できる解決策であり、C# 5.0 までは、コードを完全に非同期に書き直すよりも、この方法の方がはるかに簡単でした。

ただし、このアプローチにはいくつか問題があります。

  • あらゆる状況で機能する価値はない: 必要なスレッド プールのスレッド数は、IO の期間とサーバーの負荷に比例して決まります。残念ながら、IO の待ち時間はほとんど予測できません。次に例を示します。ASP.NET アプリケーションでサード パーティの Web サービスに HTTP 要求を行うとします。この要求は完了までに約 2 秒かかります。スレッド プールの不足が発生したため、スレッド プールのサイズを 200 スレッドに増やすと、再び正常に動作するようになります。問題は、来週、Web サービスに技術的な問題が発生し、応答時間が 10 秒に長くなる可能性があることです。突然、スレッドがブロックされる時間が 5 倍になるため、スレッド プールの不足が再び発生します。そのため、スレッド数を 5 倍の 1,000 スレッドに増やす必要があります。

  • スケーラビリティとパフォーマンス: 2 つ目の問題は、そのようにすると、依然として 1 つのリクエストにつき 1 つのスレッドが使用されることです。スレッドは高価なリソースです。.NET の各マネージ スレッドには、スタック用に 1 MB のメモリ割り当てが必要です。Web ページが 5 秒間の IO を実行し、1 秒あたり 500 リクエストの負荷がかかる場合、スレッド プールに 2,500 スレッドが必要になります。つまり、何もせずに待機するスレッドのスタック用に 2.5 GB のメモリが必要になります。次に、コンテキスト切り替えの問題があります。これは、マシンのパフォーマンスに大きな負担をかけます (Web アプリケーションだけでなく、マシン上のすべてのサービスに影響します)。Windows は待機中のスレッドを無視する点でかなり優れていますが、このような多数のスレッドを処理するようには設計されていません。実行中のスレッドの数がマシン上の論理 CPU の数 (通常は 16 未満) と等しい場合に、最高の効率が得られることに注意してください。

したがって、スレッド プールのサイズを増やすことが解決策であり、人々は 10 年にわたってこれを行ってきました (Microsoft 自身の製品でも)。ただし、メモリと CPU の使用率の点でスケーラビリティと効率性が低く、IO レイテンシが突然増加してリソース不足を引き起こす可能性に常に悩まされます。C# 5.0 までは、非同期コードの複雑さは、多くの人にとって面倒な作業ではありませんでした。async/await によってすべてが変わり、非同期 IO のスケーラビリティのメリットを享受しながら、同時にシンプルなコードを記述できるようになりました。

詳細:http://msdn.microsoft.com/en-us/library/ff647787.aspxWeb サービス呼び出しの進行中に追加の並列処理を実行する機会がある場合は、非同期呼び出しを使用して Web サービスまたはリモート オブジェクトを呼び出します。送信 Web サービス呼び出しは ASP.NET スレッド プールのスレッドを使用して行われるため、可能な場合は Web サービスへの同期 (ブロッキング) 呼び出しを避けてください。ブロッキング呼び出しにより、他の受信要求を処理するために使用できるスレッドの数が減少します。

おすすめ記事