同步和异步 I/O

另请参阅 与 I/O 相关的示例应用程序

有两种类型的输入/输出 (I/O) 同步:同步 I/O 和异步 I/O。 异步 I/O 也称为重叠 I/O。

同步文件 I/O 中,线程启动 I/O 操作,并立即进入等待状态,直到 I/O 请求完成。 执行 异步文件 I/O 的线程通过调用适当的函数将 I/O 请求发送到内核。 如果请求被内核接受,则调用线程将继续处理另一个作业,直到内核向线程发出 I/O 操作已完成的信号。 然后,它会中断其当前作业,并在必要时处理来自 I/O 操作的数据。

下图说明了这两种同步类型。

同步和异步 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 错误。 (例如,管道从另一端关闭。)
  • 例如,调用本身 (出错,一个或多个参数) 无效。
  • 进程中的另一个线程使用被阻止线程的线程句柄调用 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 是用于打开文件、磁盘卷、匿名管道和其他类似设备的最常见函数,但也可以使用句柄类型转换从其他系统对象(如套接字创建的套接字或接受函数)执行 I/O 操作。

通过使用 FILE_FLAG_BACKUP_SEMANTICS 属性调用 CreateFile 函数来获取目录对象的句柄。 目录句柄几乎从未使用过 - 备份应用程序是通常使用它们的少数应用程序之一。

为异步 I/O 打开文件对象后,必须正确创建、初始化 OVERLAPPED 结构,并将其传递到对 ReadFile 和 WriteFile 等函数的每次调用 。 在异步读取和写入操作中使用 OVERLAPPED 结构时,请记住以下事项:

  • 在完成对文件对象的所有异步 I/O 操作之前,请勿解除分配或修改 OVERLAPPED 结构或数据缓冲区。
  • 如果将指向 OVERLAPPED 结构的指针声明为局部变量,则在完成对文件对象的所有异步 I/O 操作之前,不要退出本地函数。 如果本地函数过早退出, 则 OVERLAPPED 结构将超出范围,并且该函数之外遇到的任何 ReadFileWriteFile 函数都无法访问该函数。

还可以创建事件并将句柄置于 OVERLAPPED 结构中;然后,可以使用 等待函数 通过等待事件句柄来等待 I/O 操作完成。

如前所述,使用异步句柄时,应用程序在确定何时释放与该句柄上的指定 I/O 操作关联的资源时,应小心谨慎。 如果句柄提前解除分配, ReadFileWriteFile 可能会错误地报告 I/O 操作已完成。 此外, WriteFile 函数有时会返回 TRUEGetLastError 值为 ERROR_SUCCESS,即使它使用的是异步句柄 (该句柄也可以返回 FALSEERROR_IO_PENDING) 。 习惯同步 I/O 设计的程序员通常此时会释放数据缓冲区资源,因为 TRUEERROR_SUCCESS 表示操作已完成。 但是,如果 I/O 完成端口 用于此异步句柄,则即使 I/O 操作立即完成,也会发送完成数据包。 换句话说,如果 应用程序在 WriteFile 返回 TRUE 后释放资源,除了在 I/O 完成端口例程中 ERROR_SUCCESS ,它将具有双自由错误条件。 在此示例中,建议允许完成端口例程单独负责此类资源的所有释放操作。

系统不将文件指针保留在异步句柄上,指向支持文件指针的文件和设备 (即查找设备) ,因此必须将文件位置传递给 OVERLAPPED 结构的相关偏移数据成员中的读取和写入函数。 有关详细信息,请参阅 WriteFileReadFile

同步句柄的文件指针位置在读取或写入数据时由系统维护,也可以使用 SetFilePointerSetFilePointerEx 函数进行更新。

应用程序还可以等待文件句柄来同步 I/O 操作的完成,但这样做需要极其谨慎。 每次启动 I/O 操作时,操作系统都会将文件句柄设置为未签名状态。 每次完成 I/O 操作时,操作系统都会将文件句柄设置为信号状态。 因此,如果应用程序启动两个 I/O 操作并等待文件句柄,则当句柄设置为信号状态时,无法确定完成哪个操作。 如果应用程序必须对单个文件执行多个异步 I/O 操作,则应等待每个 I/O 操作的特定 OVERLAPPED 结构中的事件句柄,而不是在通用文件句柄上。

若要取消所有挂起的异步 I/O 操作,请使用以下任一操作:

  • CancelIo - 此函数仅取消调用线程针对指定文件句柄发出的操作。
  • CancelIoEx - 此函数取消线程针对指定文件句柄发出的所有操作。

使用 CancelSynchronousIo 取消挂起的同步 I/O 操作。

ReadFileExWriteFileEx 函数使应用程序能够指定要执行的例程, (异步 I/O 请求完成时请参阅 FileIOCompletionRoutine) 。