Windows服务器端编程-第二章 设备IO与线程间通信-9-I/O完成端口对线程池的管理

I/O完成端口对线程池的管理

现在应该说说I/O完成端口为什么这么有用。首先,在创建I/O完成端口时,指定了能够并发运行的线程数量。如前所述,通常这个值为主机的CPU数量,如双CPU的机器。在产生已完成的I/O请求队列时,I/O完成端口将唤醒等待的线程。但是,完成端口最多仅会唤醒指定数量的线程。因此,如果有四个I/O请求完成了,并且有四个调用了GetQueuedCompletionStatus的线程正在等待,I/O完成端口仅仅会唤醒两个线程;其他两个线程继续休眠。被唤醒的线程处理完I/O项后,重又调用GetQueuedCompletionStatus。系统看到队列中还有更多的项,于是重新唤醒同一个线程进行处理。

如果仔细的思考一下,会注意到某些东西没有太大的意义:如果完成端口仅允许指定数量的线程被并发的唤醒,为什么还要多余的线程在线程池中等待?比如,在一个双CPU的机器上创建I/O完成端口,告诉它只允许有不超过2个的线程同时处理完成的I/O项。然后在线程池内创建4个线程(CPU数量乘以2)。结果就是看来好象创建了2个多余的,永不会被唤醒线程。

其实I/O完成端口没有那么笨。在完成端口唤醒某个线程时,会将线程ID放到与完成端口关联的第四个数据结构中:释放的线程列表 (参考表2-1)。这样完成端口会记住唤醒了哪些线程,并监视这些线程的执行情况。如果被释放的线程调用了任何函数将自己置为等待状态,完成端口会检测到并更新内部数据结构:将线程ID从释放的线程列表中移动到被暂停的线程列表中(第五个也是最后一个与I/O完成端口有关的数据结构)。

完成端口的目标是将保持可能多的――和创建完成端口时所指定的并发线程数量一样多的――线程处于被释放的线程队列中。如果一个被释放的线程出于无论任何原因又进入等待状态,被释放的线程队列会进行收缩,完成端口会释放另一个正在等待的线程。如果被暂停的线程醒来,它会离开暂停队列,重新进入被释放的线程列表。这表示被释放的线程列表可以拥有超过所允许的最大并发数量的项。


注意


一旦线程调用GetQueuedCompletionStatus,会被安排到指定的完成端口。系统假定所有被安排的线程会根据被指定的完成端口进行工作。完成端口仅在那些正在运行的被安排的线程数量少于完成端口的最大并发值时从线程池中唤醒线程。

可以通过以下三个方法之一来打断线程/完成端口的安排:

  1. 使线程退出。
  2. 使线程调用GetQueuedCompletionStatus,并传入另一个不同的I/O完成端口。
  3. 销毁线程当前被安排的I/O完成端口。
    • *

现在来个总结。假设在一个双CPU的机器上,创建一个允许不超过2个线程被并发唤醒的完成端口,然后创建4个线程等待已完成的I/O请求。如果端口上产生了3个已完成的I/O请求,仅有两个线程被唤醒来处理这些请求,以减少可运行的线程数量和节省切换时间。现在如果一个正在运行的线程调用Sleep, WaitForSingleObject, WaitForMultipleObjects, SingalObjectAndWait,同步的I/O调用,或者任何其他将导致线程离开运行态的函数,将会被I/O完成端口检测到,并立即唤醒第三个线程。完成端口的目标就是保持CPU始终处于工作状态。

最后,第三个线程将再次成为就绪态。此时,可运行的线程数量将高于系统的CPU个数。但是,完成端口会意识到这点并且不再允许更多的线程被唤醒,直到可运行的线程数量少于CPU个数。I/O完成端口架构假定可运行的线程数量仅会在短时间内超过所指定的最大数量,并且会在线程进行下一循环或再次调用GetQueuedCompletionStatus时很快回落。这样就解释了线程池内会有多于完成端口所设定数量的线程存在。

你可能感兴趣的:(c++)