完成端口 - 图文(8)

2019-09-01 11:36

熟悉或者不熟悉多线程编程的朋友,都应该知道,如果在线程睡眠的时候,简单粗暴的就把线程关闭掉的话,那是会一个很可怕的事情,因为很多线程体内很多资源都来不及释放掉,无论是这些资源最后是否会被操作系统回收,我们作为一个C++程序员来讲,都不应该允许这样的事情出现。

所以我们必须得有一个很优雅的,让线程自己退出的办法。 这时会用到我们这次见到的与完成端口有关的最后一个API,叫 PostQueuedCompletionStatus(),从名字上也能看得出来,这个是和

GetQueuedCompletionStatus() 函数相对的,这个函数的用途就是可以让我们手动的添加一个完成端口I/O操作,这样处于睡眠等待的状态的线程就会有一个被唤醒,如果为我们每一个Worker线程都调用一次PostQueuedCompletionStatus()的话,那么所有的线程也就会因此而被唤醒了。

PostQueuedCompletionStatus()函数的原型是这样定义的: [cpp] view plaincopyprint?

1. BOOL WINAPI PostQueuedCompletionStatus( 2. __in HANDLE CompletionPort,

3. __in DWORD dwNumberOfBytesTransferred, 4. __in ULONG_PTR dwCompletionKey, 5. __in_opt LPOVERLAPPED lpOverlapped 6. );

BOOL WINAPI PostQueuedComple __in __in __in __in_opt ); 我们可以看到,这个函数的参数几乎和GetQueuedCompletionStatus()的一模一样,都是需要把我们建立的完成端口传进去,然后后面的三个参数是 传输字节数、结构体参数、重叠结构的指针. 注意,这里也有一个很神奇的事情,正常情况下,GetQueuedCompletionStatus()获取回来的参数本来是应该是系统帮我们填充的,或者是在绑定完成端口时就有的,但是我们这里却可以直接使用PostQueuedCompletionStatus()直接将后面三个参数传递给GetQueuedCompletionStatus(),这样就非常方便了。

例如,我们为了能够实现通知线程退出的效果,可以自己定义一些约定,比如把这后面三个参数设置一个特殊的值,然后Worker线程接收到完成通知之后,通过判断这3个参数中是否出现了特殊的值,来决定是否是应该退出线程了。 例如我们在调用的时候,就可以这样: [cpp] view plaincopyprint?

1. for (int i = 0; i < m_nThreads; i++) 2. {

3. PostQueuedCompletionStatus(m_hIOCompletionPort, 0, (DWORD) NULL,

NULL); 4. }

for (int i = 0; i < m_nThre { PostQueuedCompletionS } 为每一个线程都发送一个完成端口数据包,有几个线程就发送几遍,把其中的dwCompletionKey参数设置为NULL,这样每一个Worker线程在接收到这个完成通知的时候,再自己判断一下这个参数是否被设置成了NULL,因为正常情况下,这个参数总是会有一个非NULL的指针传入进来的,如果Worker发现这个参数被设置成了NULL,那么Worker线程就会知道,这是应用程序再向Worker线程发送的退出指令,这样Worker线程在内部就可以自己很“优雅”的退出了…… 学会了吗? 但是这里有一个很明显的问题,聪明的朋友一定想到了,而且只有想到了这个问题的人,才算是真正看明白了这个方法。 我们只是发送了m_nThreads次,我们如何能确保每一个Worker线程正好就收到一个,然后所有的线程都正好退出呢?是的,我们没有办法保证,所以很有可能一个Worker线程处理完一个完成请求之后,发生了某些事情,结果又再次去循环接收下一个完成请求了,这样就会造成有的Worker线程没有办法接收到我们发出的退出通知。 所以,我们在退出的时候,一定要确保Worker线程只调用一次

GetQueuedCompletionStatus(),这就需要我们自己想办法了,各位请参考我在Worker线程中实现的代码,我搭配了一个退出的Event,在退出的时候SetEvent一下,来确保Worker线程每次就只会调用一轮 GetQueuedCompletionStatus() ,这样就应该比较安全了。 另外,在Vista/Win7系统中,我们还有一个更简单的方式,我们可以直接CloseHandle关掉完成端口的句柄,这样所有在GetQueuedCompletionStatus()的线程都会被唤醒,并且返回FALSE,这时调用GetLastError()获取错误码时,会返回ERROR_INVALID_HANDLE,这样每一个Worker线程就可以通过这种方式轻松简单的知道自己该退出了。当然,如果我们不能保证我们的应用程序只在Vista/Win7中,那还是老老实实的PostQueuedCompletionStatus()吧。

最后,在系统释放资源的最后阶段,切记,因为完成端口同样也是一个Handle,所以也得用CloseHandle将这个句柄关闭,当然还要记得用closesocket关闭一系列的socket,还有别的各种指针什么的,这都是作为一个合格的C++程序员的基本功,在这里就不多说了,如果还是有不太清楚的朋友,请参考我的示例代码中的 StopListen() 和DeInitialize() 函数。

六. 完成端口使用中的注意事项

终于到了文章的结尾了,不知道各位朋友是基本学会了完成端口的使用了呢,还是被完成端口以及我这么多口水的文章折磨得不行了……

最后再补充一些前面没有提到了,实际应用中的一些注意事项吧。 1. Socket的通信缓冲区设置成多大合适?

在x86的体系中,内存页面是以4KB为单位来锁定的,也就是说,就算是你投递WSARecv()的时候只用了1KB大小的缓冲区,系统还是得给你分4KB的内存。为了避免这种浪费,最好是把发送和接收数据的缓冲区直接设置成4KB的倍数。 2. 关于完成端口通知的次序问题

这个不用想也能知道,调用GetQueuedCompletionStatus() 获取I/O完成端口请求的时候,肯定是用先入先出的方式来进行的。

但是,咱们大家可能都想不到的是,唤醒那些调用了GetQueuedCompletionStatus()的线程是以后入先出的方式来进行的。

比如有4个线程在等待,如果出现了一个已经完成的I/O项,那么是最后一个调用GetQueuedCompletionStatus()的线程会被唤醒。平常这个次序倒是不重要,但是在对数据包顺序有要求的时候,比如传送大块数据的时候,是需要注意下这个先后次序的。 -- 微软之所以这么做,那当然是有道理的,这样如果反复只有一个I/O操作而不是多个操作完成的话,内核就只需要唤醒同一个线程就可以了,而不需要轮着唤醒多个线程,节约了资源,而且可以把其他长时间睡眠的线程换出内存,提到资源利用率。 3. 如果各位想要传输文件…

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

但是我们知道,ReadFile()的时候,是需要操作系统通过磁盘的驱动程序,到实际的物理硬盘上去读取文件的,这就会使得操作系统从用户态转换到内核态去调用驱动程序,然后再把读取的结果返回至用户态;同样的道理,WSARecv()也会涉及到从用户态到内核态切换的问题 --- 这样就使得我们不得不频繁的在用户态到内核态之间转换,效率低下…… 而一个非常好的解决方案是使用微软提供的扩展函数TransmitFile()来传输文件,因为只需要传递给TransmitFile()一个文件的句柄和需要传输的字节数,程序就会整个切换至内核态,无论是读取数据还是发送文件,都是直接在内核态中执行的,直到文件传输完毕才会返回至用户态给主进程发送通知。这样效率就高多了。 4. 关于重叠结构数据释放的问题

我们既然使用的是异步通讯的方式,就得要习惯一点,就是我们投递出去的完成请求,不知道什么时候我们才能收到操作完成的通知,而在这段等待通知的时间,我们就得要千万注意得保证我们投递请求的时候所使用的变量在此期间都得是有效的。

例如我们发送WSARecv请求时候所使用的Overlapped变量,因为在操作完成的时候,这个结构里面会保存很多很重要的数据,对于设备驱动程序来讲,指示保存着我们这个

Overlapped变量的指针,而在操作完成之后,驱动程序会将Buffer的指针、已经传输的字节数、错误码等等信息都写入到我们传递给它的那个Overlapped指针中去。如果我们已经不小心把Overlapped释放了,或者是又交给别的操作使用了的话,谁知道驱动程序会把这些东西写到哪里去呢?岂不是很崩溃……

暂时我想到的问题就是这么多吧,如果各位真的是要正儿八经写一个承受很大访问压力的Server的话,你慢慢就会发现,只用我附带的这个示例代码是不够的,还得需要在很多细节之处进行改进,例如用更好的数据结构来管理上下文数据,并且需要非常完善的异常处理机制等等,总之,非常期待大家的批评和指正。 谢谢大家看到这里!!!


完成端口 - 图文(8).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:铁路施工企业薪酬管理

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: