多线程笔记5

第六章:Overlapped I/O,在你身后变戏法

1.overlapped I/O 是 Win32 的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O 进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来完成 overlapped I/O。

2.Win32文件操作函数

(1)CreateFile()可以用来打开文件、串行口和并行口、Named pipes、Console。

HANDLE CreateFile(
LPCTSTR lpFileName,                                         // 指向文件名称
DWORD dwDesiredAccess,                                  // 存取模式(读或写)
DWORD dwShareMode,                                      // 共享模式(share mode)
LPSECURITY_ATTRIBUTES lpSecurityAttributes,  // 指向安全属性结构
DWORD dwCreationDisposition,                          // 如何产生
DWORD dwFlagsAndAttributes,                          // 文件属性
HANDLE hTemplateFile                                      // 一个临时文件,将拥有全部的属性拷贝
);

注:overlapped I/O性质:可以在同一时间读写文件的许多部分;多个overlapped请求,执行次序无法保证;overlapped I/O的基本型式是以ReadFile()和WriteFile()完成的。

(2)ReadFile()

BOOL ReadFile(
HANDLE hFile,                                     // 欲读之文件
LPVOID lpBuffer,                                 // 接收数据之缓冲区
DWORD nNumberOfBytesToRead,         // 欲读取的字节个数
LPDWORD lpNumberOfBytesRead,        // 实际读取的字节个数的地址
LPOVERLAPPED lpOverlapped               // 指针,指向 overlapped info
);

(3)WriteFile()

BOOL WriteFile(
HANDLE hFile,                                   // 欲写之文件
LPCVOID lpBuffer,                             // 储存数据之缓冲区
DWORD nNumberOfBytesToWrite,       // 欲写入的字节个数
LPDWORD lpNumberOfBytesWritten,   // 实际写入的字节个数的地址
LPOVERLAPPED lpOverlapped             // 指针,指向 overlapped info
);

如果CreateFile() 的第6个参数被指定为FILE_FLAG_ OVERLAPPED,就必须在上述的 lpOverlapped 参数中提供一个指针,指向一个 OVERLAPPED 结构。

(4)OVERLAPPED结构

typedef struct _OVERLAPPED {
DWORD Internal;        //通常保留。当GetOverlappedResult传回False,并且GetLastError并非传回ERROR_IO_PENDING,则内含一个视系统而定的状态。
DWORD InternalHigh; //通常被保留,当GetOverlappedResult传回True,则内含“被传输数据的长度”
DWORD Offset;          //读、写偏移位置,从文件头开始算起。若目标设备不支持文件位置,忽略
DWORD OffsetHigh;   //64位文件偏移中较高32位。若目标设备不支持文件位置,忽略
HANDLE hEvent;       //manual reset event,overlapped I/O完成时被激发。ReadFileEX,WriteFileEX忽略这个栏位,彼时被用来传递一个用户自定义的指针。
} OVERLAPPED, *LPOVERLAPPED;

OVERLAPPED 结构执行两个重要的功能。第一,它像一把钥匙,用以识别每一个目前正在进行的 overlapped 操作。第二,它在你和系统之间提供了一个共享区域,参数可以在该区域中双向传递。

通常overlapped结构存放在heap中。

3.被激发的File Handles

(1)异步IO的步骤:CreateFile指定FILE_FLAG_OVERLAPPED;设立一个OVERLAPPED结构,调用ReadFile、WriteFile带上这个参数。

(2)文件handle是一个核心对象,一旦操作完毕即被激发。

(3)GetOverlappedResult()

BOOL GetOverlappedResult(
HANDLE hFile,                                        //文件设备的handle
LPOVERLAPPED lpOverlapped,                 //一个指针,指向overlapped结构
LPDWORD lpNumberOfBytesTransferred,  //一个指针,指向DWORD,保存真正被传输的字节数。
BOOL bWait                                          //是否要等待操作完成,TRUE表示等待。
);

(4)虽然你要求一个overlapped 操作,但它并不一定就是 overlapped!如果数据已经被放进 cache中,或如果操作系统认为它可以很快速地取得那份数据,那么文件操作就会在ReadFile() 返回之前完成,而 ReadFile() 将传回 TRUE。

(5)一个文件操作为 overlapped,而操作系统把“操作请求”放到队列中等待执行, ReadFile() 和、WriteFile()都会传回 FALSE 以示失败。这个行为并不是很直观, 你必须调用GetLastError() 并确定它传回 ERROR_IO_PENDING,那意味着“overlappedI/O 请求”被放进队列之中等待执行。GetLastError() 也可能传回其他的值,例如 ERROR_HANDLE_EOF,那就真正代表一个错误了。

4.被激发的event对象

(1)所使用的 event 对象必须是手动重置(manual-reset)而非自动重置(auto-reset)。

(2)IOBYEVENT例子。

5.异步过程调用(Asynchronous Procedure Calls,APCs)

(1)使用overlapped I/O与event搭配的两个问题:

<1>WaitForMultipleObjects最多等待64个对象。

<2>必须不断的根据“哪一个handle被激发”而计算如何反应。

(2)使用Ex版的ReadFile和WriteFile,可以使用异步过程调用机制。只有当线程处于alertable状态时,APCs才会被调用。当线程因为以下5个函数而处于等待状态,且线程的“alertable”标记被设为TRUE,则线程处于alertable状态:

SleepEx()

WaitForSingleObjectEx()

WaitForMultipleObjectEx()

MsgWaitForMultipleObjectsEx()

SignalObjectAndWait();

(3)用于 overlapped I/O 的 APCs 是一种所谓的 user mode APCs。WindowsNT 另有一种所谓的 kernel mode APCs。Kernel mode APCs 也会像 usermode APCs 一样被保存起来,但一个 kernel mode APC 一定会在下一个timeslice 被调用,不管线程当时正在做什么。 Kernel mode APCs 用来处理系统机能,不在应用程序的控制之中。

(4)提供的 I/O completion routine 应该有这样的型式:

VOID WINAPI FileIOCompletionRoutine(
DWORD dwErrorCode,   //0表示操作完成,ERROR_HANDLE_EOF表示操作已经到了文件尾端。
DWORD dwNumberOfBytesTransferred,//真正被传输的数据字节数
LPOVERLAPPED lpOverlapped//指向overlapped结构,此结构由开启overlapped I/O操作的函数提供
);

(5)使用 APCs 时,OVERLAPPED 结构中的 hEvent 栏位不需要用来放置一个 event handle。Win32 文件上说此时 hEvent 栏位可以由程序员自由运用。那么最大的用途就是:首先配置一个结构,描述数据来自哪里,或是要对数据进行一些什么操作,然后将 hEvent 栏位设定指向该结构

(6)在C++ 中产生一个I/O Completion Routines:储存一个指针,指向用户自定义数据(一个对象),然后经由此指针调用一个 C++ 成员函数。由于 static 成员函数是类的一部分,你还是可以调用 private 成员函数。

6.对文件进行overlapped I/O的缺点

(1)似乎 Windows NT 是以“I/O 请求”的大小来决定要不要将此请求先记录下来。所以对于数据量小的操作,overlapped I/O的效率反而更低。

(2)解决办法:以少量的线程负责所有的硬盘 I/O,然后把这些线程的I/O 请求,保持在一个队列之中。这种效率比较高。

(3)有两种情况,overlapped I/O 总是同步执行,甚至即使 FILE_FLAG_NO_BUFFERING 已经指定。第一种情况是你进行一个写入操作而造成文件的扩展。第二种情况是你读写一个压缩文件。

7.I/O Completion Ports

(1)APCs的缺点:最大的问题就是,有好几个 I/O APIs 并不支持 APCs,如listen() 和 WaitCommEvent() 便是两个例子。APCs 的另一个问题是,只有发出“overlapped 请求”的那个线程才能够提供 callback 函数,然而在一个“scalable”(译注)系统中,最好任何线程都能够服务 events。

(2)产生一个I/O Completion Port

HANDLE CreateIoCompletionPort(
HANDLE FileHandle,                        //文件或设备的handle,若为INVALID_HANDLE_VALUE,则产生一个没有和任何handle关联的port。
HANDLE ExistingCompletionPort,      //若此栏位被指定,则FileHandle被加到此port上。指定Null产生一个新的port。
DWORD CompletionKey,                  //用户自定义的一个数值,将被交给提供服务的线程。此值和FileHanlde有关联。
DWORD NumberOfConcurrentThreads//与此I/O completion port 有关联的线程个数。
);

(3)与一个文件handle产生关联

  再次使用CreateIoCompletionPort接口。

(4)在一个I/O Completion Port上等待

BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,                        //将在其上等待completion port。
LPDWORD lpNumberOfBytesTransferred,  //指向DWORD,收到“被传输的数据字节数”。
LPDWORD lpCompletionKey,                   //指向DWORD,该DWORD将收到由CreateIoCompletionPort定义的key。
LPOVERLAPPED *lpOverlapped,               //overlapped结构指针的地址。
DWORD dwMilliseconds                          //等待的最长时间,时间终了,lpOverlapped被设为NULL,函数传回FALSE。
);

在completion port上等待的线程是以先进后出的次序提供服务。

(5)避免Completion Packets

设定一个 OVERLAPPED 结构,内含一个合法的手动重置(manual-reset)event 对象,放在 hEvent 栏位。然后把该 handle 的最低位设为 1。

overlap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

overlap.hEvent = (HANDLE)((DWORD)overlap.hEvent | 0x1);

WriteFile(hFile, buffer, 128,& dwBytesWritten, &overlap);

8.对Sockets使用Overlapped I/O

分析ECHO例子,多实践socket IOCP。

原文地址:https://www.cnblogs.com/programmer-wfq/p/4646151.html