完成端口高效的三个原因

最近看了点完成端口的内容,一点心得记录如下:


1.避免了select的查询,可以从socket直接定位到完成端口。想象同时上千个连接的程序中,别的模型里只能通过select的方式对所有的socket链接查询一次才能知道哪个socket上有事件;而完成端口模型中,一旦一个socket上有事件发生,它立即将事件组成一个完成包放入完成端口(实际上是个队列,放入完成端口的内部API是KeInsertQueue),这时等待线程直接从中取出该事件,如此即避免了一次查询。


2.相对一个链接一个线程的模型,避免了大量线程切换,能够最大限度的利用CPU。

用一个线程管理多个连接的弊端显而易见,不能同时响应多个客户请求。那么一个连接对应一个线程呢?在连接数较多的情况下,大量的CPU时间都花在了线程切换上,从而降低了CPU的利用率。完成端口模型则限定了同时运行的线程数尽量控制在(N*CPU)之内,N一般为1。这样做的好处是减少了线程切换,提高了CPU的利用率。实际上完成端口是在管理一个线程池,它会记录当前活动的(即没有被I/O等事件阻塞)线程数,一旦活动线程数低于N*CPU个,将唤醒池内的其他线程。并且唤醒次序是LIFO。另外,当一个线程完成工作后,发现完成端口队列中仍有事件,可以保持其活动状态,继续处理。


3.任何一个线程在阻塞后,都可以通知完成端口,使其能激活其他等待线程。 

完成端口之所以能够做到保持活动线程数在N*CPU之内(尽量),实际上是因为每个引起线程阻塞的API,比如ReadFile内部,都对完成端口做了处理,ReadFile中一旦发现当前线程在完成端口中,它将通知该完成端口此线程将变为非活动的,使其按需激活其他等待线程。

 

备注:

1.类Linux上的网络模型转到Windows上时可能产生明显的性能问题。一个是因为线程切换Windows不如Linux快,另一个是在类Linux系统上,socket的分配是从小到大,有小的空余则立即分配。想象这个模型,用一个数组存储socket,当链接不多时,遍历一遍socket可能很快,有效的socket都集中的数组起始部分。但Windows上的socket分配是随机的,即便只有几个链接,也可能很分散,再用数组存储socket,想访问一遍为数不多的socket,基本上也得遍历整个数组。

 

参考资料:<<深入解析Windows 第四版>>,网络资料。

你可能感兴趣的:(完成端口高效的三个原因)