完成端口(Q&A)

  1. 程序设计上,线程不怕多,怕切换的频繁,即使有多于cpu个数的线程,例如使用完成端口的时候,总会有个主线程,但这个主线程在配置完完成端口后就彻底的休息了(可以是join或信号量方式),cpu一直工作在完成端口的工作线程上,也不会频繁切换。

  2. 为什么完成端口中工作线程的数目为cpu*2?设计的目的是为了让cpu满负荷工作。会不会造成额外的线程切换?会,但正常情况下不会,这里异常就是指处理线程进入阻塞状态(Sleep()或者WaitForSingleObject()),这个时候cpu会切换到其他的线程。正常情况下,如果线程没有进入阻塞状态,在满负荷的工作,那么cpu会等待这个线程重回队列然后从队列中取出这个线程再次工作(后进先出,为了减少切换),所以正常情况下,虽然有cpu*2个线程,但是工作的线程也只有cpu数目个,不会有线程频繁切换,也可以把这些长期睡眠的线程换出内存。

  3. ACE中的前摄器使用与完成端口有些类似,WSARecv对应ACE中的ACE_Asynch_Read_Stream,调用ACE_Asynch_Read_Stream的read请求最终在windows下调用的为WSARecv方法,投递了一个读的请求。所以使用起来也差不多,在连接建立后立刻投递一个,然后其他的投递都是在工作线程中处理完之后。

  4. 完成端口在win8中有个bug,如果主线程在最后等待退出的部分用的是一个while(){getline()}想处理一些调试命令的话,如果使用AcceptEx实现完成端口,那么线程中GetQueuedCompletionStatus() 是不会读到Accept事件的,除非在cmd上输入个回车,让那个while循环执行一次。

  5. 对于完成端口的使用可以参考http://qiusuoge.com/12451.html,写的非常详细可惜没有找到对应的完整代码。说是最复杂的网络模型,但是看下来也没有什么新的概念与特殊的设计方法。之所以复杂可能与设计的比较灵活有关系。
    使用过程中涉及到几个对象线程、socket连接(文件句柄)、完成端口,然后把他们都创建好之后在关联起来就可以了(有的是创建的时候关联),设计耦合度比较松。启动后,投递一个请求(Read或者Accept(使用AcceptEx的时候有这个请求)),socket监听消息,如果收到消息通过完成端口会产生一个事件(Acept或者Read),然后通知调用GetQueuedCompletionStatus()并阻塞上面的线程,然后线程中处理,处理完后继续投递请求。如此循环。工作方式有点类似使用conditionWait的信号量+消息队列(其实这个锁与信号量结合理解也比较麻烦)。

其他的问题,直接引用别的文章中的文字:

1.至于为什么叫Overlapped?Jeffrey Richter的解释是因为“执行I/O请求的时间与线程执行其他任务的时间是重叠(overlapped)的
对于完成端口这个概念,我一直不知道为什么它的名字是叫“完成端口”,我个人的感觉应该叫它“完成队列”似乎更合适一些,总之这个“端口”和我们平常所说的用于网络通信的“端口”完全不是一个东西,我们不要混淆了。

2.而AcceptEx比Accept又强大在哪里呢?是有三点:
1)这个好处是最关键的,是因为AcceptEx是在客户端连入之前,就把客户端的Socket建立好了,也就是说,AcceptEx是先建立的Socket,然后才发出的AcceptEx调用,也就是说,在进行客户端的通信之前,无论是否有客户端连入,Socket都是提前建立好了;而不需要像accept是在客户端连入了之后,再现场去花费时间建立Socket。如果各位不清楚是如何实现的,请看后面的实现部分。

2)相比accept只能阻塞方式建立一个连入的入口,对于大量的并发客户端来讲,入口实在是有点挤;而AcceptEx可以同时在完成端口上投递多个请求,这样有客户端连入的时候,就非常优雅而且从容不迫的边喝茶边处理连入请求了。

3)AcceptEx还有一个非常体贴的优点,就是在投递AcceptEx的时候,我们还可以顺便在AcceptEx的同时,收取客户端发来的第一组数据,这个是同时进行的,也就是说,在我们收到AcceptEx完成的通知的时候,我们就已经把这第一组数据接完毕了;但是这也意味着,如果客户端只是连入但是不发送数据的话,我们就不会收到这个AcceptEx完成的通知……这个我们在后面的实现部分,也可以详细看到。

3.如果各位需要使用完成端口来传送文件的话,这里有个非常需要注意的地方。因为发送文件的做法,按照正常人的思路来讲,都会是先打开一个文件,然后不断的循环调用ReadFile()读取一块之后,然后再调用WSASend ()去发发送。

但是我们知道,ReadFile()的时候,是需要操作系统通过磁盘的驱动程序,到实际的物理硬盘上去读取文件的,这就会使得操作系统从用户态转换到内核态去调用驱动程序,然后再把读取的结果返回至用户态;同样的道理,WSARecv()也会涉及到从用户态到内核态切换的问题 — 这样就使得我们不得不频繁的在用户态到内核态之间转换,效率低下……

而一个非常好的解决方案是使用微软提供的扩展函数TransmitFile()来传输文件,因为只需要传递给TransmitFile()一个文件的句柄和需要传输的字节数,程序就会整个切换至内核态,无论是读取数据还是发送文件,都是直接在内核态中执行的,直到文件传输完毕才会返回至用户态给主进程发送通知。这样效率就高多了。

参考:
http://www.cnblogs.com/pen-ink/articles/1834088.html
http://blog.csdn.net/ithzhang/article/details/8508161
http://qiusuoge.com/12451.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值