IOCP出现的原因:
windows系统的设计目标就是设计一个出色的,安全健壮的系统。为各种程序提供服务,这时候就出现了两种模型:
1)串行模型:一个线程等待一个客户发出的请求,当请求到来的时候,线程唤醒然后对请求处理。
2)并行模型:一个线程等待一个客户发生的请求,当请求到来的时候,创建新的线程去对请求处理,而原先的线程就会再次陷入等待状态。
串行模型的问题是不能同时处理多个请求。如果发生多个请求时,一次只能处理一个请求,效率很低。不过ping服务器就时这种模型。
并行模型为了突破串行模型的问题,创建多个处理请求的线程,能够很容易的体现出多处理器的优势。
并行模型的问题是在同时处理多个客户发来的请求时,系统中会存在很多并行执行状态的线程,由于线程的上下背景文切换会导致浪费大量的cpu时间,以至于分给各个线程的cpu时间就会很少,效率也会变低,但是为了解决这一问题,微软提出了IOCP内核对象。
IOCP出现的原因:为了解决并发模型切换上下背景文浪费太多时间的情况。背后的理论是:限制并发运行的线程的最大数量。一般是cpu数量*2(cpu数量通过GetSystemInfo获取)
《Windows核心编程》中提出IOCP内核对象是最最麻烦的内核对象,windows中利用CreateIOCompletionPort函数来创建IO完成端口时,系统创建了5个相关的数据结构:设备列表、IO完成队列(先进先出)、等待线程队列(后进先出)、已释放线程列表、已暂停线程列表。
添加 | 删除 | |
设备列表 | CreateIOCompletionPort被调用 | clsoehandle释放IOCP句柄 |
IO完成队列 | 1)IO请求完成、2)PostQueuedCompletionStatus被调用 | 在等待线程队列的线程唤醒,并取出一个IO请求 |
等待线程队列 | GetPostCompletionStatus被调用 | 在IO完成队列非空,且运行的线程小于并发线程限制数量时,线程从GetPostCompletionStatus处返回执行。IO完成队列会删除取出的项,该线程加入已释放线程队列 |
已释放线程列表 | 1)等待队列中的线程被唤醒 2)暂停的线程被唤醒 | 1)线程再次调用GetPostCompletionStatus,从已释放线程队列移除,加入等待线程队列 2)线程挂起,加入已暂停线程列表中 |
已暂停线程列表 | 线程将自己挂起 | 挂机线程被唤醒,移除已暂停的线程列表,加入已释放线程列表 |
整个过程相当于
1.创建IO完成端口
HANDLE WINAPI CreateIoCompletionPort(
__in HANDLE FileHandle, //文件 设备句柄
__in_opt HANDLE ExistingCompletionPort, //与设备关联的IO完成端口句柄,为NULL时,系统会创建新的完成端口
__in ULONG_PTR CompletionKey, //完成键,用它来区分各个设备。
__in DWORD NumberOfConcurrentThreads //允许运行的最大线程数量,如果传0表示允许并发执行的线程数量等于CPU主机数量(我的本机是4核8线程,计算机将CPU主机数量当作了8)
);
这个函数会完成两个任务: 一是创建一个IO完成端口对象,二是将一个设备与一个IO完成端口关联起来。
2.将已完成的IO请求投递到IO完成端口的队列
PostQueuedCompletionStatus(
_In_ HANDLE CompletionPort, //完成端口的句柄
_In_ DWORD dwNumberOfBytesTransferred,
_In_ ULONG_PTR dwCompletionKey,
_In_opt_ LPOVERLAPPED lpOverlapped
);
后三个参数是为调用了那个GetQueuedCompletionStatus的线程而准备的。
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再次回到等待线程队列中中。(即当前线程属于等待线程队列中的一员了)