I/O 完了ポート

I/O 完了ポートは、マルチプロセッサ システムで複数の非同期 I/O 要求を処理するための効率的なスレッドモデルを提供します。 プロセスが I/O 完了ポートを作成すると、システムは、これらの要求を処理することを唯一の目的とするスレッドに関連付けられたキュー オブジェクトを作成します。 多くの同時非同期 I/O 要求を処理するプロセスでは、I/O 要求を受信した時点でスレッドを作成するよりも、事前に割り当てられたスレッド プールと組み合わせて I/O 完了ポートを使用することで、より迅速かつ効率的に行うことができます。

I/O 完了ポートのしくみ

CreateIoCompletionPort 関数は、I/O 完了ポートを作成し、1 つ以上のファイル ハンドルをそのポートに関連付けます。 これらのファイル ハンドルの 1 つに対する非同期 I/O 操作が完了すると、I/O 完了パケットは、関連付けられている I/O 完了ポートに対して先入れ先出し (FIFO) 順にキューに入れられます。 このメカニズムの強力な用途の 1 つは、複数のファイル ハンドルの同期ポイントを 1 つのオブジェクトに結合することですが、他にも便利なアプリケーションがあります。 パケットは FIFO 順にキューに入れられますが、異なる順序でデキューされる可能性があることに注意してください。

Note

ここで使用される ファイル ハンドル という用語は、ディスク上のファイルだけでなく、重複した I/O エンドポイントを表すシステム抽象化を指します。 たとえば、ネットワーク エンドポイント、TCP ソケット、名前付きパイプ、メール スロットなどです。 重複した I/O をサポートするすべてのシステム オブジェクトを使用できます。 関連する I/O 関数の一覧については、このトピックの最後を参照してください。

 

ファイル ハンドルが完了ポートに関連付けられている場合、パケットが完了ポートから削除されるまで、渡された状態ブロックは更新されません。 唯一の例外は、元の操作がエラーで同期的に返される場合です。 スレッド (メイン スレッドまたは メイン スレッド自体によって作成されたもの) は、GetQueuedCompletionStatus 関数を使用して、非同期 I/O が完了するまで直接待機するのではなく、完了パケットが I/O 完了ポートにキューに入れるのを待機します。 I/O 完了ポートでの実行をブロックするスレッドは、先入れ先出し (LIFO) の順序で解放され、次の完了パケットはそのスレッドの I/O 完了ポートの FIFO キューからプルされます。 つまり、完了パケットがスレッドに解放されると、システムはそのポートに関連付けられている最後の (最新の) スレッドを解放し、最も古い I/O 完了の完了情報を渡します。

指定した I/O 完了ポートに対して任意の数のスレッドが GetQueuedCompletionStatus を呼び出すことができますが、指定したスレッドが 初めて GetQueuedCompletionStatus を 呼び出すと、次の 3 つのいずれかが発生するまで、指定された I/O 完了ポートに関連付けられます。スレッドは終了するか、別の I/O 完了ポートを指定するか、または I/O 完了ポートを閉じます。 つまり、1 つのスレッドを、最大で 1 つの I/O 完了ポートに関連付けることができます。

完了パケットが I/O 完了ポートにキューに登録されると、システムはまず、そのポートに関連付けられているスレッドの数を確認します。 実行中のスレッドの数がコンカレンシー値より少ない場合 (次のセクションで説明します)、待機中のスレッドの 1 つ (最新のスレッド) で完了パケットを処理できます。 実行中のスレッドが処理を完了すると、通常は GetQueuedCompletionStatus を再度呼び出します。その時点で、次の完了パケットを返すか、キューが空の場合は待機します。

スレッドは PostQueuedCompletionStatus 関数を使用して、I/O 完了ポートのキューに完了パケットを配置できます。 これにより、完了ポートを使用して、I/O システムからの I/O 完了パケットの受信に加えて、プロセスの他のスレッドからの通信を受信できます。 PostQueuedCompletionStatus 関数を使用すると、アプリケーションは、非同期 I/O 操作を開始せずに、独自の特殊な目的の完了パケットを I/O 完了ポートにキューに入れます。 これは、たとえば、外部イベントのワーカー スレッドに通知する場合に便利です。

I/O 完了ポート ハンドルと、その特定の I/O 完了ポートに関連付けられているすべてのファイル ハンドルは、 I/O 完了ポートへの参照と呼ばれます。 I/O 完了ポートは、それ以上参照がない場合に解放されます。 したがって、I/O 完了ポートとそれに関連付けられているシステム リソースを解放するには、これらのハンドルをすべて適切に閉じる必要があります。 これらの条件が満たされた後、アプリケーションは CloseHandle 関数を呼び出して I/O 完了ポート ハンドルを閉じる必要があります。

注意

I/O 完了ポートは、それを作成したプロセスに関連付けられているため、プロセス間で共有できません。 ただし、1 つのハンドルは、同じプロセス内のスレッド間で共有可能です。

 

スレッドとコンカレンシー

慎重に考慮すべき I/O 完了ポートの最も重要なプロパティは、コンカレンシー値です。 完了ポートのコンカレンシー値は、NumberOfConcurrentThreads パラメーターを使用して CreateIoCompletionPort を使用して作成されるときに指定されます。 この値は、完了ポートに関連付けられている実行可能スレッドの数を制限します。 完了ポートに関連付けられている実行可能スレッドの合計数がコンカレンシー値に達すると、実行可能なスレッドの数がコンカレンシー値を下回るまで、システムはその完了ポートに関連付けられている後続のスレッドの実行をブロックします。

最も効率的なシナリオは、キューで待機している完了パケットがあるが、ポートがコンカレンシー制限に達したために待機を満たさない場合に発生します。 GetQueuedCompletionStatus 関数呼び出しで待機している 1 つ以上のスレッドのコンカレンシー値で何が起こるかを検討してください。 この場合、キューに常に完了パケットが待機している場合、実行中のスレッドが GetQueuedCompletionStatus を呼び出すとき、スレッド キューは LIFO であるため、実行はブロックされません。 代わりに、このスレッドは、キューに登録された次の完了パケットをすぐに取得します。 実行中のスレッドが継続的に完了パケットを取得しており、他のスレッドを実行できないため、スレッド コンテキストの切り替えは行われません。

注意

前の例では、余分なスレッドは役に立たず、実行されないように見えますが、実行中のスレッドが他のメカニズムによって待機状態になることはないことを前提としています。それ以外の場合は、関連付けられている I/O 完了ポートを終了します。 アプリケーションを設計するときは、このようなスレッド実行の影響をすべて考慮してください。

 

コンカレンシー値に最適な全体的な最大値は、コンピューター上の CPU の数です。 トランザクションで長い計算が必要な場合は、コンカレンシー値を大きくすると、より多くのスレッドを実行できるようになります。 各完了パケットの完了には時間がかかる場合がありますが、同時に処理される完了パケットが増えます。 コンカレンシー値をプロファイリング ツールと組み合わせて試して、アプリケーションに最適な効果を得ることができます。

また、 GetQueuedCompletionStatus で待機しているスレッドは、同じ I/O 完了ポートに関連付けられている別の実行中のスレッドが、 SuspendThread 関数などの他の理由で待機状態になった場合に、完了パケットを処理することもできます。 待機状態のスレッドが再び実行を開始すると、アクティブなスレッドの数がコンカレンシー値を超える短い期間が発生する可能性があります。 ただし、アクティブなスレッドの数がコンカレンシー値を下回るまで、新しいアクティブなスレッドを許可しないことで、システムはこの数をすばやく減らします。 これは、アプリケーションがコンカレンシー値よりも多くのスレッドをスレッド プールに作成する理由の 1 つです。 スレッド プールの管理は、このトピックの範囲を超えていますが、経験則として、システム上のプロセッサと同じ数のスレッドをスレッド プール内に少なくとも 2 倍にすることをお勧めします。 スレッド プールの詳細については、「スレッド プール」を参照してください。

サポートされている I/O 関数

次の関数を使用して、I/O 完了ポートを使用して完了する I/O 操作を開始できます。 I/O 完了ポート メカニズムを有効にするには、関数に OVERLAPPED 構造体のインスタンスと、以前に I/O 完了ポートに関連付けられたファイル ハンドル ( CreateIoCompletionPort の呼び出し) を渡す必要があります。

プロセスとスレッドについて

BindIoCompletionCallback

CreateIoCompletionPort

GetQueuedCompletionStatus

GetQueuedCompletionStatusEx

PostQueuedCompletionStatus