スレッドプール
スレッド プール は、アプリケーションに代わって非同期コールバックを効率的に実行するワーカー スレッドのコレクションです。 スレッド プールは主に、アプリケーション スレッドの数を減らし、ワーカー スレッドを管理するために使用されます。 アプリケーションは、作業項目をキューに入れたり、作業を待機可能なハンドルに関連付けたり、タイマーに基づいて自動的にキューに入れたり、I/O とバインドしたりできます。
スレッドプールアーキテクチャ
スレッド プールを使用すると、次のアプリケーションでメリットが得られます。
- 高度に並列化されており、多数の小さな作業項目を非同期にディスパッチできるアプリケーション (分散インデックス検索やネットワーク I/O など)。
- それぞれが短時間実行される多数のスレッドを作成および破棄するアプリケーション。 スレッド プールを使用すると、スレッド管理の複雑さと、スレッドの作成および破棄に伴うオーバーヘッドが軽減されます。
- 独立した作業項目をバックグラウンドで並行して処理するアプリケーション (複数のタブの読み込みなど)。
- カーネル オブジェクトに対して排他的待機を実行するか、オブジェクトに対する着信イベントをブロックする必要があるアプリケーション。 スレッド プールを使用すると、スレッド管理の複雑さが軽減され、コンテキスト スイッチの回数が減るためパフォーマンスが向上します。
- イベントを待機するためのカスタム ウェイター スレッドを作成するアプリケーション。
元のスレッド プールは Windows Vista で完全に再設計されました。 新しいスレッド プールは、単一のワーカー スレッド タイプ (I/O と非 I/O の両方をサポート) を提供し、タイマー スレッドを使用せず、単一のタイマー キューを提供し、専用の永続スレッドを提供するという点で改善されています。 また、クリーンアップ グループ、より高いパフォーマンス、プロセスごとに独立してスケジュールされる複数のプール、および新しいスレッド プール API も提供されます。
スレッド プールのアーキテクチャは次の要素で構成されます。
- コールバック関数を実行するワーカースレッド
- 複数の待機ハンドルを待機するウェイタースレッド
- 作業キュー
- 各プロセスのデフォルトのスレッドプール
- ワーカースレッドを管理するワーカーファクトリー
ベスト プラクティス
新しい スレッド プール API は、 元のスレッド プール APIよりも柔軟性と制御性に優れています。 ただし、微妙ではあるが重要な違いがいくつかあります。 元の API では待機のリセットは自動的に行われていましたが、新しい API では待機を毎回明示的にリセットする必要があります。 元の API は、偽装を自動的に処理し、呼び出しプロセスのセキュリティ コンテキストをスレッドに転送しました。 新しい API では、アプリケーションはセキュリティ コンテキストを明示的に設定する必要があります。
スレッド プールを使用する場合のベスト プラクティスは次のとおりです。
プロセスのスレッドはスレッド プールを共有します。 単一のワーカー スレッドは、複数のコールバック関数を一度に 1 つずつ実行できます。 これらのワーカー スレッドはスレッド プールによって管理されます。 したがって、スレッド上で TerminateThread を呼び出したり、コールバック関数から ExitThread を呼び出したりして、スレッド プールからスレッドを終了しないでください。
I/O 要求はスレッド プール内の任意のスレッドで実行できます。 スレッド プールのスレッドで I/O をキャンセルするには、同期が必要です。これは、キャンセル関数が I/O 要求を処理しているスレッドとは別のスレッドで実行される可能性があり、その結果、不明な操作がキャンセルされる可能性があるためです。 これを回避するには、非同期 I/O のために CancelIoEx を呼び出すときに、I/O 要求が開始された OVERLAPPED 構造体を常に提供するか、独自の同期を使用して、 CancelSynchronousIo または CancelIoEx 関数を呼び出す前に、ターゲット スレッドで他の I/O が開始されないようにします。
関数から戻る前に、コールバック関数で作成されたすべてのリソースをクリーンアップします。 これらには、TLS、セキュリティ コンテキスト、スレッドの優先順位、COM 登録が含まれます。 コールバック関数も、戻る前にスレッドの状態を復元する必要があります。
スレッド プールがハンドルの処理を終了したことを通知するまで、待機ハンドルとそれに関連付けられたオブジェクトを存続させます。
時間のかかる操作 (I/O フラッシュやリソースのクリーンアップなど) を待機しているすべてのスレッドをマークして、スレッド プールがこのスレッドを待機する代わりに新しいスレッドを割り当てることができるようにします。
スレッド プールを使用する DLL をアンロードする前に、すべての作業項目、I/O、待機操作、およびタイマーをキャンセルし、実行中のコールバックが完了するまで待機します。
作業項目間およびコールバック間の依存関係を排除し、コールバックが完了するまで待機しないようにし、スレッドの優先度を維持することで、デッドロックを回避します。
デフォルトのスレッド プールを使用する他のコンポーネントを含むプロセスで、あまり多くの項目をあまり早くキューに入れないでください。 Svchost.exe を含むプロセスごとに 1 つのデフォルトのスレッド プールがあります。 デフォルトでは、各スレッド プールには最大 500 個のワーカー スレッドがあります。 準備完了/実行中の状態のワーカー スレッドの数がプロセッサの数よりも少ない必要がある場合、スレッド プールは、より多くのワーカー スレッドを作成しようとします。
COM シングルスレッド アパートメント モデルはスレッド プールと互換性がないため、使用しないでください。 STA は、スレッドの次の作業項目に影響を与える可能性のあるスレッド状態を作成します。 STA は一般に長寿命であり、スレッド プールの反対であるスレッド アフィニティを備えています。
新しいスレッド プールを作成して、スレッドの優先順位と分離を制御し、カスタム特性を作成し、応答性を向上させることができます。 ただし、スレッド プールを追加すると、より多くのシステム リソース (スレッド、カーネル メモリ) が必要になります。 プールが多すぎると、CPU 競合の可能性が高まります。
可能であれば、スレッド プールのスレッドにシグナルを送信するには、APC ベースのメカニズムではなく、待機可能なオブジェクトを使用します。 APC は、システムがスレッド プール スレッドの有効期間を制御するため、他のシグナリング メカニズムほどスレッド プール スレッドではうまく機能しません。そのため、通知が配信される前にスレッドが終了する可能性があります。
スレッド プール デバッガー拡張機能 !tp を使用します。 このコマンドの使用方法は次のとおりです。
- プール アドレス フラグ
- obj アドレス フラグ
- tqueue アドレス フラグ
- ウェイター アドレス
- ワーカー アドレス
プール、ウェイター、ワーカーの場合、アドレスがゼロの場合、コマンドはすべてのオブジェクトをダンプします。 ウェイターとワーカーの場合、アドレスを省略すると現在のスレッドがダンプされます。 次のフラグが定義されています: 0x1 (単一行出力)、0x2 (ダンプ メンバー)、および 0x4 (ダンプ プール作業キュー)。
関連トピック