1.16 IO完成端口
URL:IOCP IO完成端口 - 沉疴 - 博客园 (cnblogs.com)
URL:【Windows原理】异步IO-完成端口(IOCPs
demo:.\ StudyDemo\CompletionPortTest
IO完成端口是windows下性能最好的IO模型。windows下的socket也是一种IO,所以完成端口也是最好的socket IO模型。
1.16.1 IO完成端口原理
IO完成端口的出现是为了解决并发模型中可运行线程上下文切换开销过大而出现的。
在《Windows核心编程》的描述中,IO完成端口是Wnidows系统提供的最复杂的内核对象,是一种解决并发IO请求的最佳模型,是用来实现高容量网路服务器的最佳方法。既然是一个对象,那么就直接分析一下操作系统眼中的完成端口的具体定义吧。Windows中利用CreateIoCompletionPort命令创建完成端口对象时,系统内部自动创建了5个相应的数据结构,分别是:
设备列表(Device List)、
IO完成请求队列(I/O Completion Queue-FIFO)、
等待线程队列(WaitingThread List-LIFO)、
释放线程队列(Released Thread List)、
暂停线程队列(Paused Thread List)。
1.16.2 IO完成端口相关函数API
1.创建IO完成端口
HANDLE WINAPI CreateIoCompletionPort(
//文件 设备句柄
__in HANDLE FileHandle,
//与设备关联的IO完成端口句柄,为NULL时,系统会创建新的完成端口
__in_opt HANDLE ExistingCompletionPort,
//完成键,用它来区分各个设备。
__in ULONG_PTR CompletionKey,
//允许运行的最大线程数量,如果传0
表示允许并发执行的线程数量等于CPU主机数量
(我的本机是4核8线程,计算机将CPU主机数量当作了8)
__in DWORD NumberOfConcurrentThreads
);
这个函数会完成两个任务: 一是创建一个IO完成端口对象,二是将一个设备与一个IO完成端口关联起来。
CreateIoCompletionPort使用示例:
// 创建完成端口
HANDLE hComplePort = CreateIoCompletionPort(
INVALID_HANDLE_VALUE,// 创建完成端口时,不用传递设备对象句柄
NULL,// 已经创建完成端口,穿件完成端口时,需要填NULL
0,// 创建完成端口时,不用传
si.dwNumberOfProcessors // 处理器数量,传0时使用默认的个数
);
// 将文件对象和完成端口进行管理
CreateIoCompletionPort(
hFile, // 要关联的设备对象
hComplePort, // 要进行关联的完成端口
1, // 完成键
0 // 进行关联时不用传
);
// 上面两部可以通过下面一次调用完成
/*HANDLE hComplePort = CreateIoCompletionPort(hFile, NULL, 1, si.dwNumberOfProcessors);*/
绑定后,IO操作完成即触发完成队列,此时GetQueuedCompletionStatus 可收到绑定的CompletionKey 参数值,异步作业中可以以此作为流程分发。
- 将已完成的IO请求投递到IO完成端口的队列
PostQueuedCompletionStatus(
_In_ HANDLE CompletionPort, //完成端口的句柄
_In_ DWORD dwNumberOfBytesTransferred,
_In_ ULONG_PTR dwCompletionKey,
_In_opt_ LPOVERLAPPED lpOverlapped
);
后三个参数是为调用了那个GetQueuedCompletionStatus的线程而准备的。调用此处和IO动作完成触发相同操作。
3.GetQueuedCompletionStatus函数在检查IO完成队列里是否有已经完成的IO请求。
BOOL WINAPI GetQueuedCompletionStatus(
_In_ HANDLE CompletionPort, //完成端口句柄
_Out_ LPDWORD lpNumberOfBytesTransferred,
_Out_ PULONG_PTR lpCompletionKey,
_Out_ LPOVERLAPPED * lpOverlapped,
_In_ DWORD dwMilliseconds //等待时间
);
1)在等待线程队列中的线程调用GetQueuedCompletionStatus检查IO完成队列里是否有已经完成的IO请求时,如果IO完成队列中存在已完成的IO请求,则GetQueuedCompletionStatus先删除IO完成队列中这个对应的项,然后将线程ID转移到已释放线程列表中(即当前线程属于已释放列表中的一员了)
(2)在已释放列表中的线程调用GetQueuedCompletionStatus检查IO完成队列里是否有已经完成的IO请求时,如果IO完成队列中不再存在已完成的IO请求,则线程ID再次回到等待线程队列中中。(即当前线程属于等待线程队列中的一员了)
当IO完成后或者直接调用了PostQueuedCompletionStatus 触发线程释放,函数则返回并进行执行。
1.16.3 OVERLAPPED结构
OVERLAPPED即OVERLAPPED是一个包含了用于异步输入输出的信息的结构体。
外文名:OVERLAPPED
功 能:用于异步输入输出的信息的结构体
结构声明:
OVERLAPPED结构类型声明如下:
第一种声明
typedef struct _OVERLAPPED
{
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED
参数说明
Internal: 预留给操作系统使用。它指定一个独立于系统的状态,当GetOverlappedResult函数返回时没有设置扩展错误信息ERROR_IO_PENDING时有效。
InternalHigh: 预留给操作系统使用。它指定长度的数据转移,当GetOverlappedResult函数返回TRUE时有效。
Offset: 该文件的位置是从文件起始处的字节偏移量。调用进程设置这个成员之前调用ReadFile或WriteFile函数。当读取或写入命名管道和通信设备时这个成员被忽略设为零。
OffsetHigh: 指定文件传送的字节偏移量的高位字。当读取或写入命名管道和通信设备时这个成员被忽略设为零。
hEvent: 在转移完成时处理一个事件设置为有信号状态。调用进程集这个成员在调用ReadFile、 WriteFile、TransactNamedPipe、 ConnectNamedPipe函数之前。
第二种声明
typedef struct _OVERLAPPED
{
ULONG_PTR Internal; //操作系统保留,指出一个和系统相关的状态
ULONG_PTR InternalHigh; //指出发送或接收的数据长度
union
{
struct
{
DWORD Offset; //文件传送的字节偏移量的低位字
DWORD OffsetHigh; //文件传送的字节偏移量的高位字
};
PVOID Pointer; //指针,指向文件传送位置
};
HANDLE hEvent; //指定一个I/O操作完成后触发的事件
} OVERLAPPED, *LPOVERLAPPED;
结构作用
I/O设备处理必然让主程序停下来干等I/O的完成,解决这个问题,可以使用OVERLAPPED。
OVERLAPPED I/O是WIN32的一项技术, 你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来I/O完成OVERLAPPED I/O。你可以获得线程的所有利益,而不需付出什么痛苦的代价。也就是说,OVERLAPPED主要是设置异步I/O操作,异步I/O操作是指应用程序可以在后台读或者写数据,而在前台做其他事情。