1. 背景
构建一个服务应用程序,常用的主要有两种模型: 串行模型和并行模型。
1)串行模型: 一个线程等待一个请求,当请求到达时,线程被唤醒对请求进行处理;处理完后再接着等待下一个请求。
缺点:不能同时处理多个请求。
2)并行模型:一个线程等待一个请求,当请求到达时,线程会创建一个新的线程来处理请求。然后线程进入下一次循环等待下一个请求。新的线程处理完请求后,会自行终止。
缺点:如果请求数量较多,需要创建相对数量的线程,一方面线程频繁地创建、终止会带来开销;另一方面,线程数量过多,但实际上机器的CPU数量有限,且这些线程大多都是处于可调度的状态,那么内核需花费很多在线程的切换上下文的过程,导致没有多少CPU时间来执行真正需要的任务。 I/O完成端口内核对象可解决此类问题。
2. 创建I/O完成端口
I/O完成端口会对并发运行的线程数量设定一个上限,即线程数量不能随着请求个数增加而线性增长,一般是根据机器本身的CPU个数来选择一个合适的上限,以减少内核执行线程上下文切换的花销。在应用程序初始化的时候,会创建一个线程池,并让线程池中的线程在应用程序运行期间处于可调用度状态,得以提高服务应用程序的性能。
创建I/O完成端口的函数用: CreateIoCompletionPort(),此函数有参数可指定线程池的大小。当创建一个I/O完成端口时,系统内核会创建5个不同的数据结构:
1)设备列表:表示与该端口相关联的一个或多个设备;
2)I/O完成队列(先入先出):当设备的一个异步I/O请求完成时,设备会检查该设备是否与一个I/O完成端口相关联,如果是则将该已完成的I/O请求追加到I/O完成队列;
3)等待线程队列(后入先出):当线程池中的每个线程调用GetQueueCompletionStatus()时,调用线程的线程ID会被添加到等待线程队列,这是告知I/O完成端口内核对象,有哪些线程在等待处理已完成的I/O请求,当I/O完成队列中出现一项时,I/O完成端口就会唤醒等待线程队列中的一个线程来进行处理。该队列是后入先出的,假设有3个线程在等待线程队列中,当I/O完成队列出现一个项时,则最后调用GetQueueCompletionStatus()的线程3会被唤醒来处理这个项。线程3处理完这个项后,会再次调用GetQueueCompletionStatus()进入等待线程队列,这时如果I/O完成队列又来了一项,则线程3会被再次唤醒来处理这个项。
4)已释放线程列表:当完成端口在等待线程队列中唤醒的线程或已暂停的线程被唤醒,都会进入到已释放线程列表;
5)已暂停线程列表:如果一个已释放的线程,在工作过程中切换到了等待状态,则会被放入到已暂停线程列表。