次の方法で共有


同期および非同期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 構造体へのポインタが ReadFile および WriteFile の呼び出しに渡されます。 同期 I/O を実行する場合、 ReadFile および WriteFileの呼び出しではこの構造体は必要ありません。

Note

ファイルまたはデバイスが非同期 I/O 用に開かれている場合、そのハンドルを使用する WriteFile などの関数への後続の呼び出しは通常すぐに戻りますが、ブロックされた実行に関しては同期的に動作することもできます。 詳細については、「非同期ディスク I/O が Windows で同期として表示される」を参照してください。

CreateFile は、ファイル、ディスク ボリューム、匿名パイプ、およびその他の同様のデバイスを開くために使用する最も一般的な関数ですが、 socket または accept 関数によって作成されたソケットなど、他のシステム オブジェクトからのハンドル typecast を使用して 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 関数は、非同期ハンドルを使用しているにもかかわらず、 ERROR_SUCCESSGetLastError 値で TRUE を返すことがあります (ERROR_IO_PENDINGFALSE を返す場合もあります)。 同期 I/O 設計に慣れているプログラマーは通常、この時点でデータ バッファー リソースを解放します。これは、 TRUEERROR_SUCCESS が操作が完了したことを示すためです。 ただし、この非同期ハンドルで I/O 完了ポート が使用されている場合、I/O 操作が直ちに完了した場合でも完了パケットが送信されます。 つまり、アプリケーションが、I/O 完了ポート ルーチンに加えて、 WriteFileTRUE を返し、 ERROR_SUCCESS を返した後にリソースを解放すると、二重解放エラー状態になります。 この例では、完了ポート ルーチンがそのようなリソースのすべての解放操作を単独で実行できるようにすることがレコメンデーションとなります。

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

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

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

保留中のすべての非同期 I/O 操作をキャンセルするには、次のいずれかを使用します。

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

保留中の同期 I/O 操作をキャンセルするには、 CancelSynchronousIo を使用します。

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