スレッド プール

スレッド プールは、アプリケーションに代わって非同期コールバックを効率的に実行するワーカー スレッドのコレクションです。 スレッド プールは、主にアプリケーション スレッドの数を減らし、ワーカー スレッドの管理を提供するために使用されます。 アプリケーションは、作業項目をキューに入れ、作業を待機可能なハンドルに関連付け、タイマーに基づいて自動的にキューに入れ、I/O とバインドすることができます。

スレッド プールのアーキテクチャ

次のアプリケーションでは、スレッド プールを使用するとメリットがあります。

  • 非常に並列で、多数の小さな作業項目を非同期的にディスパッチできるアプリケーション (分散インデックス検索やネットワーク I/O など)。
  • 各スレッドが短時間実行される多数のスレッドを作成および破棄するアプリケーション。 スレッド プールを使用すると、スレッド管理の複雑さと、スレッドの作成と破棄に関連するオーバーヘッドを軽減できます。
  • 独立した作業項目をバックグラウンドで並行して処理するアプリケーション (複数のタブの読み込みなど)。
  • カーネル オブジェクトに対して排他的待機を実行するか、オブジェクトの受信イベントをブロックする必要があるアプリケーション。 スレッド プールを使用すると、コンテキストスイッチの数を減らすことで、スレッド管理の複雑さを軽減し、パフォーマンスを向上させることができます。
  • イベントを待機するカスタム ウェイター スレッドを作成するアプリケーション。

元のスレッド プールは、Windows Vista で完全に再設計されています。 新しいスレッド プールは、単一のワーカー スレッドの種類 (I/O と非 I/O の両方をサポート) を提供し、タイマー スレッドを使用せず、1 つのタイマー キューを提供し、専用の永続的なスレッドを提供するため、改善されています。 また、クリーングループ、パフォーマンスの向上、個別にスケジュールされたプロセスごとの複数のプール、新しいスレッド プール API も提供されます。

スレッド プールのアーキテクチャは、次で構成されます。

  • コールバック関数を実行するワーカー スレッド
  • 複数の待機ハンドルを待機するウェイター スレッド
  • 作業キュー
  • 各プロセスの既定のスレッド プール
  • ワーカー スレッドを管理するワーカー ファクトリ

ベスト プラクティス

新しい スレッド プール API は、 元のスレッド プール API よりも柔軟性と制御性を提供します。 ただし、微妙ですが重要な違いがいくつかあります。 元の API では、待機リセットは自動でした。新しい API では、毎回待機を明示的にリセットする必要があります。 元の API は偽装を自動的に処理し、呼び出し元プロセスのセキュリティ コンテキストをスレッドに転送します。 新しい API では、アプリケーションでセキュリティ コンテキストを明示的に設定する必要があります。

スレッド プールを使用する場合のベスト プラクティスを次に示します。

  • プロセスのスレッドはスレッド プールを共有します。 1 つのワーカー スレッドで複数のコールバック関数を一度に 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、待機操作、タイマーを取り消し、コールバックの実行が完了するまで待機します。

  • 作業項目とコールバック間の依存関係を排除し、コールバックがそれ自体の完了を待たないようにし、スレッドの優先順位を維持することで、デッドロックを回避します。

  • 既定のスレッド プールを使用する他のコンポーネントを使用して、プロセス内でキューに入れすぎるアイテムを短時間でキューに入れすぎないようにしてください。 プロセスごとに 1 つの既定のスレッド プール (Svchost.exeを含む) があります。 既定では、各スレッド プールには最大 500 個のワーカー スレッドがあります。 スレッド プールは、準備完了/実行中状態のワーカー スレッドの数がプロセッサの数より少ない必要がある場合に、より多くのワーカー スレッドを作成しようとします。

  • COM シングルスレッド アパートメント モデルはスレッド プールと互換性がなさがあるため、避けてください。 STA は、スレッドの次の作業項目に影響を与える可能性があるスレッド状態を作成します。 STA は一般に有効期間が長く、スレッド アフィニティを持っています。これはスレッド プールの反対です。

  • スレッドの優先度と分離を制御し、カスタム特性を作成し、応答性を向上させる新しいスレッド プールを作成します。 ただし、追加のスレッド プールには、より多くのシステム リソース (スレッド、カーネル メモリ) が必要です。 プールが多すぎると、CPU 競合の可能性が高くなります。

  • 可能であれば、APC ベースのメカニズムの代わりに待機可能オブジェクトを使用して、スレッド プール スレッドを通知します。 システムがスレッド プール スレッドの有効期間を制御するため、通知が配信される前にスレッドを終了できるため、APC はスレッド プール スレッドと同様に他のシグナルメカニズムと同様に機能しません。

  • スレッド プール デバッガー拡張機能 !tp を使用します。 このコマンドの使用法は次のとおりです。

    • プール アドレスフラグ
    • obj アドレスフラグ
    • tqueue アドレスフラグ
    • waiter アドレス
    • ワーカー アドレス

    プール、ウェイター、ワーカーの場合、アドレスが 0 の場合、コマンドはすべてのオブジェクトをダンプします。 waiter と worker の場合、アドレスを省略すると、現在のスレッドがダンプされます。 次のフラグが定義されています: 0x1 (単一行出力)、0x2 (ダンプ メンバー)、および0x4 (ダンプ プール作業キュー)。

スレッド プール API

スレッド プール関数の使用