同期と非同期の I/O

I/O 関連のサンプル アプリケーションも参照してください。

入出力 (I/O) 同期には、同期 I/O と非同期 I/O の 2 種類があります。 非同期 I/O は、重複した I/O とも呼ばれます。

同期ファイル I/O では、スレッドは I/O 操作を開始し、I/O 要求が完了するまですぐに待機状態になります。 非同期ファイル I/O を実行するスレッドは、適切な関数を呼び出して、カーネルに I/O 要求を送信します。 カーネルが要求を受け入れた場合、呼び出し元スレッドは、カーネルが I/O 操作が完了したことをスレッドに通知するまで、別のジョブの処理を続行します。 次に、現在のジョブを中断し、必要に応じて I/O 操作のデータを処理します。

次の図に、2 つの同期の種類を示します。

同期および非同期 i/o

大規模なデータベースの更新やバックアップ、低速通信リンクなど、I/O 要求に大量の時間がかかることが予想される状況では、通常、非同期 I/O は処理効率を最適化するのに適した方法です。 ただし、比較的高速な I/O 操作の場合、カーネル I/O 要求とカーネル信号を処理するオーバーヘッドにより、非同期 I/O のメリットが低下する可能性があります。特に、多くの高速 I/O 操作を行う必要がある場合です。 この場合、同期 I/O の方が適しています。 これらのタスクを実行する方法のメカニズムと実装の詳細は、使用されるデバイス ハンドルの種類とアプリケーションの特定のニーズによって異なります。 つまり、通常、この問題を解決する方法は複数あります。

同期および非同期の I/O に関する考慮事項

同期 I/O 用にファイルまたはデバイスを開いた場合 (つまり、 FILE_FLAG_OVERLAPPED が指定されていません)、 WriteFile などの関数の後続の呼び出しでは、次のいずれかのイベントが発生するまで呼び出し元スレッドの実行をブロックできます。

  • I/O 操作が完了します (この例では、データ書き込み)。
  • I/O エラーが発生します。 (たとえば、パイプはもう一方の端から閉じています)。
  • 呼び出し自体でエラーが発生しました (たとえば、1 つ以上のパラメーターが無効です)。
  • プロセス内の別のスレッドは、ブロックされたスレッドのスレッド ハンドルを使用して CancelSynchronousIo 関数を呼び出します。これにより、そのスレッドの I/O が終了し、I/O 操作が失敗します。
  • ブロックされたスレッドはシステムによって終了されます。たとえば、プロセス自体が終了するか、ブロックされたスレッドのハンドルを使用して別のスレッドが TerminateThread 関数を呼び出します。 (これは一般的に最後の手段と見なされ、適切なアプリケーション設計ではありません。

場合によっては、この遅延がアプリケーションの設計と目的に受け入れられない可能性があるため、アプリケーション 設計者は、I/O 完了ポートなどの適切なスレッド同期オブジェクトで非同期 I/O を使用することを検討する必要があります。 スレッド同期の詳細については、「 同期について」を参照してください。

プロセスは、dwFlagsAndAttributes パラメーターに FILE_FLAG_OVERLAPPED フラグを指定して、CreateFile の呼び出しで非同期 I/O 用のファイルを開きます。 FILE_FLAG_OVERLAPPEDが指定されていない場合、ファイルは同期 I/O 用に開かれます。 非同期 I/O 用にファイルを開くと、 OVERLAPPED 構造体へのポインターが ReadFileWriteFile の呼び出しに渡されます。 同期 I/O を実行する場合、 ReadFileWriteFile の呼び出しでは、この構造体は必要ありません。

注意

非同期 I/O 用にファイルまたはデバイスを開いた場合、そのハンドルを使用した WriteFile などの関数に対する後続の呼び出しは、通常は直ちに返されますが、ブロックされた実行に対して同期的に動作することもできます。 詳細については、「https://support.microsoft.com/kb/156932」を参照してください。

 

CreateFile は、ファイル、ディスク ボリューム、匿名パイプ、およびその他の同様のデバイスを開くために使用する最も一般的な関数ですが、ソケットによって作成されたソケットやaccept 関数など、他のシステム オブジェクトからのハンドル 型キャストを使用して I/O 操作を実行することもできます。

ディレクトリ オブジェクトへのハンドルは、FILE_FLAG_BACKUP_SEMANTICS属性を使用して CreateFile 関数を呼び出すことによって取得されます。 ディレクトリ ハンドルはほとんど使用されません。バックアップ アプリケーションは、通常、それらを使用する数少ないアプリケーションの 1 つです。

非同期 I/O のファイル オブジェクトを開いた後、 OVERLAPPED 構造体を適切に作成、初期化し、 ReadFileWriteFile などの関数の各呼び出しに渡す必要があります。 非同期の読み取りおよび書き込み操作で OVERLAPPED 構造体を使用する場合は、次の点に注意してください。

  • ファイル オブジェクトに対するすべての非同期 I/O 操作が完了するまで、 OVERLAPPED 構造体またはデータ バッファーの割り当てを解除または変更しないでください。
  • OVERLAPPED 構造体へのポインターをローカル変数として宣言する場合は、ファイル オブジェクトに対するすべての非同期 I/O 操作が完了するまで、ローカル関数を終了しないでください。 ローカル関数が途中で終了すると、 OVERLAPPED 構造体はスコープ外になり、その関数の外部で検出された ReadFile 関数または WriteFile 関数にはアクセスできなくなります。

イベントを作成し、 ハンドルを OVERLAPPED 構造体に配置することもできます。その後、 待機関数 を使用して、イベント ハンドルを待機することで I/O 操作が完了するまで待機できます。

前に説明したように、非同期ハンドルを操作する場合、アプリケーションでは、そのハンドルで指定された I/O 操作に関連付けられているリソースを解放するタイミングを決定するときに注意する必要があります。 ハンドルの割り当てが途中で解除されると、 ReadFile または WriteFile が I/O 操作が完了したことを誤って報告する可能性があります。 さらに、WriteFile 関数は、非同期ハンドルを使用している場合でも、getLastErrorが ERROR_SUCCESSTRUE を返す場合があります (ERROR_IO_PENDINGFALSE を返すこともできます)。 同期 I/O 設計に慣れているプログラマは、通常、この時点でデータ バッファー リソースを解放します。 これは、TRUEERROR_SUCCESS 操作が完了したことを示しているためです。 ただし、この非同期ハンドルで I/O 完了ポート が使用されている場合、I/O 操作が直ちに完了した場合でも、完了パケットも送信されます。 言い換えると、WriteFile が I/O 入力候補ポート ルーチンに加えて ERROR_SUCCESSTRUE を返した後にアプリケーションがリソースを解放すると、エラー状態が 2 倍になります。 この例では、このようなリソースに対するすべての解放操作に対して完了ポート ルーチンが単独で責任を負うことをお勧めします。

システムは、ファイル ポインター (つまり、デバイスのシーク) をサポートするファイルおよびデバイスへの非同期ハンドルに対するファイル ポインターを維持しないため、ファイル位置は 、OVERLAPPED 構造体の関連オフセット データ メンバー内の読み取りおよび書き込み関数に渡す必要があります。 詳細については、「 WriteFile 」および「 ReadFile」を参照してください。

同期ハンドルのファイル ポインター位置は、データの読み取りまたは書き込み時にシステムによって維持され、SetFilePointer または SetFilePointerEx 関数を使用して更新することもできます。

アプリケーションは、ファイル ハンドルを待機して I/O 操作の完了を同期することもできますが、これを行うには細心の注意が必要です。 I/O 操作が開始されるたびに、オペレーティング システムはファイル ハンドルを非署名状態に設定します。 I/O 操作が完了するたびに、オペレーティング システムはファイル ハンドルをシグナル状態に設定します。 そのため、アプリケーションが 2 つの I/O 操作を開始し、ファイル ハンドルで待機する場合、ハンドルがシグナル状態に設定されたときにどの操作が完了するかを判断する方法はありません。 アプリケーションで 1 つのファイルに対して複数の非同期 I/O 操作を実行する必要がある場合は、共通ファイル ハンドルではなく、I/O 操作ごとに特定の OVERLAPPED 構造体のイベント ハンドルを待機する必要があります。

保留中のすべての非同期 I/O 操作を取り消すには、次のいずれかを使用します。

  • CancelIo — この関数は、指定されたファイル ハンドルに対して呼び出し元スレッドによって発行された操作のみを取り消します。
  • CancelIoEx — この関数は、指定されたファイル ハンドルに対してスレッドによって発行されたすべての操作を取り消します。

CancelSynchronousIo を使用して、保留中の同期 I/O 操作を取り消します。

ReadFileEx 関数と WriteFileEx 関数を使用すると、非同期 I/O 要求が完了したときに実行するルーチンをアプリケーションで指定できます (FileIOCompletionRoutine を参照)。