• Chapter10“I/O设备的同步和异步”之异步I/O简介


    异步I/O基础

           相比于计算机上的其他操作,I/O操作时最慢的最不可预测的操作之一。如果使用同步I/O,虽然方便控制,但是浪费了大量的CPU时间;而异步I/O在一定程度上缓解了这个问题。
           异步I/O就是将I/O请求发送给设备驱动器,让设备驱动器负责实际的I/O操作,当设备驱动器在等待I/O设备相应时,应用程序的线程不用被挂起去等待I/O操作的完成,线程可以跳过等待继续执行其他任务。异步I/O的关键就是将所有的I/O请求队列化,然后以异步的方式执行I/O操作,在I/O操作完成之后再通知相应的程序。
           如果编程使用异步I/O操作?首先,你在使用CreateFile函数打开或创建设备时,需要在dwFlagsAndAttributes参数指定FILE_FLAG_OVERLAPPED标志;然后在ReadFile或WriteFile函数的最后一个参数pOverlapped传入指定OVERLAPPED结构体。

    OVERLAPPED结构体

           结构体原型:

    typedef struct _OVERLAPPED {
    	DWORD Internal; // [out] Error code
    	DWORD InternalHigh; // [out] Number of bytes transferred
    	DWORD Offset; // [in] Low 32-bit file offset
    	DWORD OffsetHigh; // [in] High 32-bit file offset
    	HANDLE hEvent; // [in] Event handle or data
    } OVERLAPPED, *LPOVERLAPPED;
           结构体的三个成员——Offset,OffsetHigh,hEvent——需要在调用CreateFile或ReadFile函数之前进行初始化的,而其他两个成员——Internal,InternalHigh——是执行I/O操作完成后设备驱动器负责设置的。

           Offset &OffsetHigh:当一个文件被访问时,这两个成员组成一个64位的偏移量。请注意与同步I/O的不同,同步I/O时每个文件句柄都有一个与之对应的文件指针,标记每次文件读写的起始位置。而异步I/O由于其异步性,不能保证每次操作的顺序,如果不将每次读写的位置指定好,那么将会导致读写位置不确定,读写的内容也可能不是想到的。所以在异步I/O操作时,文件指针是直接被忽略的。

           hEvent:事件句柄它记录了当I/O操作完成事通知的对象。具体的使用请看下面的”触发一个事件内核对象”。

           Internal:这个成员标记了I/O操作处理过程中的错误码。当引发一个异步I/O请求时,设备将Internal置为STATUS_PENDING,表示没有错误出现(因为I/O操作还没开始)。事实上,WinBase.h中的宏HasOverlappedIoCompleted可以检测异步I/O操作是否完成,如果I/O请求没有完成,就返回FALSE.如果I/O操作完成则返回TRUE,宏定义如下:
                          #define HasOverlappedIoCompleted(pOverlapped) \
                                               ((pOverlapped)->Internal !=STATUS_PENDING)

           InternalHigh:这个值,在I/O操作完成时,记录I/O操作的字节数。

    异步I/O警告

           对于异步I/O操作,有几点你需要注意:

           第一:I/O设备驱动器对于队列化的I/O请求不一定要按照First-in First-out的顺序处理。

           第二:很有必要对异步I/O用合适的方式进行错误检测。

           第三:启用异步I/O请求的数据缓存区(data buffer)和OVERLAPPED结构体,不能在I/O请求完成之前被移动或销毁。因为当设备驱动器队列化I/O请求时,会记录数据缓冲区或OVERLAPPED结构体的地址(注意是地址,而不是内容,因为复制内容代价太高),所以如果你移动或销毁其内容,后果不堪设想。

          上面的第三点非常重要,请看下面的代码:

    VOID ReadData(HANDLE hFile) { 
    	OVERLAPPED o = { 0 }; 
    	BYTE b[100]; 
    	ReadFile(hFile, b, 100, NULL, &o); 
    }
           咋一看,你可能觉得上面的代码No problem。但是对照上面的第三点注意事项你就会发现不对,在ReadData函数体内,OVERLAPPED结构体或数据缓冲区b[100]都是分配在函数的stack里,当函数返回后这些数据都会被释放,而I/O操作时异步的,在函数退出后执行异步I/O操作时发现指针指向数据缓冲区和OVERLAPPED结构体的内容已经无效了,这显然就会出错。

    取消队列化的I/O请求

           有时,你可能需要在设备驱动器处理I/O请求之前取消某个已经队列化的I/O请求,Windows提供了几种取消的方式:

    1)       调用CancelIo函数取消特定句柄相关的所有队列化的I/O请求.
               BOOL CancelIo(HANDLE hFile)

    2)       你可以通过关闭设备本身来取消所有的I/O请求,不管哪个线程管理这些请求.

    3)       当一个线程消亡时,系统会自动取消该线程的所有I/O请求(除了那些已经绑定到相应的I/O端口)。

    4)       还可以调用CancelIoEx函数取消给定句柄发起的一个单独的特定的I/O请求。CancelIoEx函数原型:
               BOOL CancelIoEx(HANDLE hFile, LPOVERLAPPED pOverlapped)。

    值得注意的是:由于每次异步I/O都要指定一个OVERLAPPED结构体,所以每次调用CancelIoEx函数将取消一个I/O请求,但是如果pOverlapped为NULL,那个该函数就会取消hFile句柄相关的所有的异步I/O请求。

  • 相关阅读:
    dbutils关于连接维护的问题Q
    触发器
    mysql的full join的实现
    mysql exists 和 in的效率比较
    浏览器禁用Cookie后的session处理
    自定义org.apache.commons.beanutils的类型转换器
    Java中形参个数可变的方法
    递归方法的重要规定——递归一定要向己知方向递归
    抽象工厂模式——肯德基消费
    异常链
  • 原文地址:https://www.cnblogs.com/java20130722/p/3207128.html
Copyright © 2020-2023  润新知