让这个 bug 困扰了很久,前一段太忙只找了个临时解决方案而没有追究原因,今天终于把它搞清楚了。由于测试时只在多 CPU 系统上出现,我甚至一度怀疑它是 CPU 的 bug😂 。
两个 c/s 结构的网络通讯程序,服务器端使用完成端口模型,客户端使用阻塞模型,双方以一种客户端发送命令,服务器端处理,然后返回应答的方式通讯。问题出在服务器端。以下是服务器端代码的大致处理逻辑:
long volatile g_busy = 0;
void iocp_thread()
{
while( GetQueuedCompletionStatus() )
{
if( InterlockedCompareExchange( &g_busy, 1, 0 ) != 0 )
WSASend( "服务器忙" );
// 处理命令
ProcessCommand();
WSASend( "应答信息" );
InterlockedExchange( &g_busy, 0 );
}
}
其中 ProcessCommand
需要互斥运行(这是简化的逻辑,实际上有很多不同的命令,有些需要互斥,有些可以并行,否则就没必要用完成端口了),并且需要一定的时间才能处理完毕。为了避免多个客户端同时执行命令,导致所有的 iocp 线程都等在那,我把 g_busy
当成了一个锁,第一个线程可以成功进入,其它的都直接向客户端返回“服务器忙”。
程序一直都运行的很好,直到有一天把服务器程序装到了一台双核的机器上。我发现,如果让客户端连续发送命令,即收到上一条命令的应答后立即发送下一条命令,就会随机的返回“服务器忙”,而这时只有一个客户端连接上去,按照我设想的逻辑是不可能出这种情况的。检查了半天代码,没觉得有什么问题,调试吧,又遇到了另一个难题,海森堡的测不准原理起作用了,做的工作太多问题就消失了,做的太少又得不到什么有价值的信息。搞得我很是头疼。
今天再次看这个问题,突然想到:它肯定和线程切换相关,所以我应该记录下每次处理命令的线程的 ID,这样出错时就可以看看上次成功执行命令的那个线程在干什么了。方法正确了,问题也就迎刃而解了,我发现,出问题时,上一个线程的 WSASend
居然还没有返回,也就是说,客户端已经收到应答并发送了下一条命令,服务器端也收到了命令并准备处理,但上一条命令的应答却还没有完全发送完成,难怪出错了!
总结经验教训,感觉自己一开始被两点给误导了,一是实际程序中的 WSARecv
/WSASend
藏的比较深,没这么明显,所以没注意到。二是当时粗略检查代码觉得没问题,就把主要精力放在 ProcessCommand
上了,由于我把它里面一段访问数据库的代码注释掉以后,问题就不出了,所以还看了半天 ATL OLEDB 的源码,最后精疲力尽,其它事情又比较多就放弃了。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于