第十章:IO完成端口

来源:互联网 发布:java实现支付功能代码 编辑:程序博客网 时间:2024/04/29 20:23

1:创建IO完成端口

1:线程数量等于CPU数量时效率最高

2:线程的创建是有开销的,所以应该使用线程池

3:创建函数:

HANDLE CreateIoCompletionPort(HANDLE FileHandle,HANDLE ExistingCompletionPort,ULONG_PTR CompletionKey,                      DWORD NumberOfConcurrentThreads//如果传递0,则线程数量=CPU数量);//完成端口是内核对象,但它没有安全描述符

2:将设备与IO完成端口关联起来

1:一般情况下,会将创建IO完成端口和将设备与IO完成端口关联起来两者分离,代码如下

HANDLE hd=CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);CreateIoCompletionPort(hFile,hd,dwKey,0);

2:IO完成端口5个数据结构

[1:设备列表]

与完成端口相关联的<设备列表,完成键>对

[2:IO完成队列]

这是一个先进先出队列,当一个异步IO完成后,系统会调用PostQueuedCompletionStatus()投递到此队列末尾

我们可以让一个已完成的异步IO不投递到IO完成队列,比如向一个套接字发送数据,我们不关心是否发送完成,如下代码可以产生这样的效果

Overlapped.hEvent=CreateEvent();Overlapped.hEvent=(HANDLE)((DWORD_PTR)Overlapped.hEvent|1);ReadFile(...,&Overlapped);...;CloseHandle((HANDLE)Overlapped.hEvent&~1);

[插曲]
线程池中的线程调用

BOOL GetQueuedCompletionStatus(HANDLE CompletionPort,LPDWORD lpNumberOfBytes,PULONG_PTR lpCompletionKey,LPOVERLAPPED* lpOverlapped,DWORD dwMilliseconds);

从IO完成队列中获取一个已经完成的异步IO,如果没有,线程会进入睡眠状态,我们应该在一个循环中调用此函数

线程池线程数量经验值是CPU*2

[3:等待线程队列]

此队列是后入先出队列
当线程调用GetQueuedCompletionStatus()后,线程的ThreadID被加入此队列,当IO完成队列出现一项时,此队列中一个线程会被唤醒

之所以使用后入先出队列,是因为如果几个线程长时间未被唤醒,将被唤出内存和CPU cache,节约内存

//等待线程中处理DWORD dwNumBytes;ULONG_PTR CompletionKey;OVERLAPPED* pOverlapped;//初始化BOOL bOK=GetQueuedCompletionStatus(hdCompletionPort,&dwNumBytes,&CompletionKey,&pOverlapped,1000);DWORD dwError=GetLastError();if(bOK){//成功得到IO完成队列中的一项}else{if(pOverlapped=NULL){//IO出现错误,查看dwError确定错误}else{if(dwError==WAIT_TIMEOUT){//超时}else{//查看dwError确定错误}}}

Vista中提供GetQueuedCompletionStatusEx()让一个线程处理多个IO完成队列,这样当负荷非常大时,可以避免频繁的线程切换所带来的开销

当我们对一个完成端口设备发出一个异步IO请求,系统一定会将结果添加到完成端口的IO完成队列中,这样做目的是给程序员提供一个一致的编程模型,我们可以通过调用SetFileCompletionNotificationModes()并传入FILE_SKIP_COMPLETION_PORT_ON_SUCCESS告诉系统不要将以同步方式完成的请求放入IO完成队列中

可以调用SetFileIoOverlappedRange()将OVERLAPPED所用的内存锁定,使其不能被唤出内存,这样可提高性能

[4:已释放线程队列]

[5:已暂停线程队列]

当等待线程队列中个一个线程被唤醒,此线程就进入已释放线程队列,此时已释放线程队列个数不会大于最大运行运行线程数量,当已释放线程队列中一个线程因为调用了一个函数而被挂起,此线程会进入已暂停线程队列,IO完成端口会唤醒一个等待线程队列中的线程,以补满已释放线程队列,这样CPU便能满负荷运行,如果一个线程从已暂停线程队列回到已释放线程队列,此时已释放线程队列中线程数量就会大于允许的最大线程数量,但这个状态不会保持很长时间,因为当线程任务结束后,会回到等待线程队列中去

3:线程池中的线程个数

可以采用启发式算法动态调整

4:说明

vista之前的系统是:当一个线程投递了一些完成端口异步请求,当这些请求未完成时,线程不能被终止,如果强行终止,这些请求会被取消

vista之后,线程终止后,这些请求仍然会被投递到IO完成队列中

5:投递一个模拟的已完成的IO请求

BOOL PostQueuedCompletionStatus(  HANDLE CompletionPort,  DWORD dwNumberOfBytesTransferred,  ULONG_PTR dwCompletionKey,  LPOVERLAPPED lpOverlapped);

这个函数可以通知所有等待线程应该退出了,为每个线程调用此函数,在线程循环中判断完成键(我的设想,书上写的是判断完成返回值),然后线程退出循环

Vista中,调用CloseHande(完成端口句柄);系统会将所有正在等待GetQueuedCompletionStatus()返回的线程唤醒,并返回FALSE,此时调用GetLastError()==ERROR_INVALID_HANDLE,线程就知道应该退出了