完成端口,详情请见: http://blog.csdn.net/ithzhang/article/details/8508161
HANDLE CreateIoCompletionPort(
HANDLE hFile,
HANDLE hExistingCompletionPort,
ULONG_PTR CompletionKey,
DWORD dwNumberOfConcurrentThreads);
该函数主要完成两件事:
1、创建一个完成端口
2、将设备与一个IO完成端口关联起来
hFile: 就是设备句柄
hExistingCompletionPort: 是与设备相关联的完成端口句柄,当传入NULL时,将创建一个全新的完成端口。
CompletionKey: 是一个用户关心的值,但是系统并不关心传入的是什么。主要用于区分是设备的,如可以传入:READ_KEY、WRITE_KEY、BW_FILE等,同样也可以类似于线程函数中的参数,传入一个this指针,在外面拿到这个指针进行强转,就可以得到对应的类/变量等。
dwNumberOfConcurrentThreads: 告诉操作系统,共有多少个线程并发。如果传入的参数为0,那么取的是默认值,即CPU个数。
因此CreateIoCompletionPort()可以抽象成以下两个函数:
</pre><p></p><p></p><pre name="code" class="cpp">// 创建一个新的完成端口
HANDEL CreateNewCompletionPort(DWORD dwNumberOfConcurrentThreads)
{
return (CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, dwNumberOfConcurrentThreads));
}
// 将设备与IO完成端口关联起来
BOOL AssociateDeviceWithCompletionPort(HANDLE hCompletionPort, HANDLE hDevice, DWORD dwCompletionKey)
{
HANDLE h = CreateIoCompletionPort(hDevice, hCompletionPort, dwCompletionKey, 0);
returun (h == hCompletionPort);
}
CreateIoCompletionPort()相关的几个数据结构:
1、设备列表
每次调用CreateIoCompletionPort()时,系统会判断传入的hExistingCompletionPort是否为NULL, 如果为NULL则重新创建一个IO完成端口。然后为些完成端口创建设备列表,并将设备(hFile)添加到设备列表中(先进先出)
2、IO完成队列
当设备的一个异步IO操作完成后,系统会去检测该设备是否与一个IO完成端口有关联;如果有关联,系统会将这个已完成的IO操作添加到完成端口的完成队列中去。IO完成队列,每一项包括:已传输的字节数、关键项CompletionKey(通过关键项可以拿到是谁完成了操作,可以强转此参数)、以及一个指向IO指求的OVERLAPPED结构指针及错误码。
insert队列条件: I/O请求完成时,调用PostQueuedCompletionStatus()。
delete队列条件:完成端口从等待队列中删除一项时。
Windows为完成端口提供了一个函数,该函数可以将线程切换到睡眠状态,来等待设备IO操作完成并进行完成端口。该函数是阻塞的。
BOOL GetQueuedCompletionStatus(
HANDLE hCompletionPort,
PDWORD pdwNumberOfBytesTransferred,
ULONG_PTR pCompletionKey,
OVERLAPPED** ppOverlapped,
DWORD dwMilliSeconds);
hCompeltionPort: 表示线程希望对哪个完成端口进行监测。 GetQueuedCompletionStatus()的任务就是将调用线程切换到睡眠状态,也就是阻塞在此函数上,直到指定的完成端口出现一项完成或是超时。
pdwNumberOfBytesTransfered: 返回在异常IO操作完成时已传输的字节数。
pCompletionKey: 返回完成键,通过强转可以到用户关心的类/成员。
ppOverlapped: 返回异步IO操作开始时,传入的Overlapped结构地址。
dwMilliSeconds: 等待时间。
该函数成功反回True,否则返回False
3、等待线程队列
当线程池中的每个线程调用GetQueuedCompletionStatus()时,调用线程的标识符就会被添加到这个等待线程队列中,这使得IO完成端口知道有哪些线程当前正在等待对已完成的IO请求进行处理。
当IO完成端口的IO完成队列中出现一项时,完成端口会唤醒等待队列中的一个线程。这个线程会得到已完成IO项的所有信息,包括:已传输字节数、完成键、OVERLAPPED结构地址。这些信息是通过GetQueueCompletionStatus()的参数返回得到的。
insert队列条件: 线程调用GetQueuedCompletionStatus()
delete队列条件:IO完成队列不为空,且正在运行的线程数小于最大允许并发数(通常为CPU数量)
IO完成队列的各项是先进先出,但是唤醒等待线程队列中的线程是按照后进先出的方式进行。假设有四个线程正在等待队列中,如果出现一个完成项,那么四个等待线程中最后调用GetQueuedCompletionStatus()的那个线程将会被唤醒来处理这一项。当处理完这一项后,线程会由于再次调用GetQueuedCompletionStatus()而进入到等待队列中。
使用这种算法,就可以得到哪些线程是长时间没有工作,长时间睡眠的。
4、已释放线程队列
存储着已被唤醒的线程句柄。完成端口在等待线程队列中唤醒一个线程,或是已暂停的线程被唤醒时。
insert队列条件:1、完成端口在等待线程队列中唤醒一个线程。 2、已暂停的线程再度被唤醒
delete队列条件:1、线程再次调用GetQueueCompletionStatus() 进入等待线程队列。 2、线程调用一个函数(如Sleep, WaitForSingleObject)等自己挂起,进入已暂停线程队列
5、已暂停线程队列
已释放的线程调用一个函数将自己挂起时,会将此线程ID插入到已暂停线程队列中。
insert队列条件: 已释放的线程调用一个函数将自己挂起
delete队列条件:已挂的线程再度被唤醒,进入已释放线程队列
总的来说,各线程队列转换关系如下:
3delete --> 4
4delete --> 3
4delete --> 5
5delete --> 4
模拟已完成的IO请求
BOOL PostQueuedCompletionStatus(
HANDLE hCompletionPort,
DWORD dwNumBytes,
ULONG_PTR CompletionKey,
OVERLAPPED*pOverlapped);
这个函数用来将一个已完成的IO通知追加到IO完成端口的队列中。
hCompletionPort:表示我们要将该项加到哪个完成端口的队列中。
剩下的3个参数表示应该返回什么值给那个调用了GetQueuedCompletionStatus()的线程。
当线程从IO完成队列中得到一个模拟项的时候,GetQueuedCompletionStatus()会返回TRUE,表求IO请求成功执行。