什么是完成包?
完成包,即IO Completion Packet,是指异步IO操作完毕后OS提交给应用层的通知包。IOCP维护了一个IO操作结果队列,里面
保存着各种完成包。应用层调用GQCS(也就是GetQueueCompletionStatus)函数获取这些完成包。
最大并发线程数
在一个典型的IOCP程序里,会有一些线程调用GQCS去获取IO操作结果。最大并发线程数指定在同一时刻处理完成包的线程数目。
该参数在调用CreateIoCompletionPort时由NumberOfConcurrentThreads指定。
工作者线程
工作者线程一般指的就是调用GQCS函数的线程。要注意的是,工作者线程数和最大并发线程数并不是同一回事(见下文)。工作者
线程由应用层显示创建(_beginthreadex 之类)。工作者线程通常是一个循环,会不断地GQCS到完成包,然后处理完成包。
调度过程
工作者线程以是否阻塞分为两种状态:运行状态和等待状态。当线程做一些阻塞操作时(线程同步,甚至GQCS空的完成队列),线程
处于等待状态;否则,线程处于运行状态。
另一方面,OS会始终保持某一时刻处于运行状态的线程数小于最大并发线程数。每一个调用GQCS函数的线程OS实际上都会进行记录,
当完成队列里有完成包时,OS会首先检查当前处于运行状态的工作线程数是否小于最大并发线程数,如果小于,OS会按照LIFO的顺
序让某个工作者线程从GQCS返回(此工作者线程转换为运行状态)。如何决定这个LIFO?这是简单地通过调用GQCS函数的顺序决定的。
从这里可以看出,这里涉及到线程唤醒和睡眠的操作。如果两个线程被放置于同一个CPU上,就会有线程切换的开销。因此,为了消
除这个开销,最大并发线程数被建议为设置成CPU数量。
从以上调度过程还可以看出,如果某个处于运行状态的工作者线程在处理完成包时阻塞了(例如线程同步、其他IO操作),那么就有
CPU资源处于空闲状态。因此,我们也看到很多文档里建议,工作者线程数为(CPU数*2+2)。
在一个等待线程转换到运行状态时,有可能会出现短暂的时间运行线程数超过最大并发线程数,这个时候OS会迅速地让这个新转换
的线程阻塞,从而减少这个数量。(关于这个观点,MSDN上只说:by not allowing any new active threads,却没说明not allowing
what)