Windows下MFC程序利用LockCop解决死锁

死锁现象:在训练的时候,点击“终止”按钮时不时会发生死锁。

检测工具:LockCop、TRACE宏、::GetCurrentThreadID函数。

检测手段:

总结起来就是——

  第一步:用LockCop找哪几个线程死锁起来了,因为什么对象死锁的(比如说那些用于线程同步的工具类或者某些要Block的函数等等)。比如我这里就是两个线程6616和5112因为CCriticalSection和SendMessage锁死的,见图3;具体来说,我的程序里用了一个CCriticalSection m_cs对象。一个UI线程(主线程),一个Worker线程(跑算法)。Worker线程要往UI的CConsoleDialog写数据,具体来说,就是Worker线程要往UI的CConsoleDialog的Edit Control通过SetDlgItemText写入CString。因为我可能在后面的修改中Worker线程可能有多个,所以我在这个代码段用m_cs对象给锁起来了,如图1。

  第二步:用TRACE宏和::GetCurrentThreadID函数结合第一步找出的死锁对象CCriticalSection和SendMessage,找到每个线程具体死锁的代码位置。具体来说,我把所有CCriticalSection::Lock的前面都加了TRACE和::GetCurrentThreadID,由于使用SendMessage的地方太多,就没有加。TRACE给出的结果如图4所示,主线程ID是6616,Worker线程ID是5112。

  通过TRACE的结果可以看出线程6616最后死锁在代码170行的位置,因为最后一次TRACE输出是在169行;同理可以看出,线程5112最后死锁在代码70行的位置。

  第三步:分析。这里就要具体问题具体分析了。

  就我这个问题而言,

  第一方面,主线程6616对Stop函数的调用是由于我的Dialog上的一个CButton的点击而触发的;显然点击CButton会对Dialog进行SendMessage,而SendMessage在结束之前是会向内核申请CConsoleDialog的锁的,这也是SendMessage会block的原因(显然Windows让SendMessage会block对线程安全是有好处的,否则UI界面在接收多个Worker线程的数据的时候就会乱套)。另外注意观察图2的代码,Stop里面有一截代码是用m_cs锁起来了的,主线程6616在申请m_cs锁的时候死锁了,这时候主线程6616持有对CConsoleDialog的内核锁。

  第二方面,显然Worker线程5112一定是死锁在图1的代码段中的,因为线程5112的最后一个TRACE就在那里面输出,因为我们已经知道主线程6616是死锁在申请m_cs的锁的时候,所以结合LockCop的检测结果我们知道线程5112一定是死锁在申请SendMessage内核锁的时候。观察图1,就知道,唯一可能调用SendMessage的位置就是SetDlgItemText,线程5112在调用SetDlgItemText的时候是持有m_cs的锁的,但是CConsoleDialog的内核锁已经被主线程6616持有。这样就发生了相互等待,死锁就产生了。

  最后我是把SetDlgItemText的调用不再放到m_cs锁的区域内就解决死锁问题了,打破了死锁的闭环。

  LockCop的基本使用见这篇博文:http://blog.csdn.net/segen_jaa/article/details/7843654

LockCop通过下载Windows核心编程的源码编译可以得到,我是在CSDN下载的:http://download.csdn.net/detail/fksec/4209539

 

图1 死锁代码1

 

图2 死锁代码2

 

图3 LockCop检测结果

 

图4 TRACE结果

  刚刚把前面说的死锁问题解决,测试了一下发现不止那一个死锁问题,打得一手好脸。通过同样的过程发现(我在所有可能block的地方前后都加了TRACE和::GetCurrentThreadID,也就是说除了m_cs.Lock以外,还有一个::WaitForSingleObject),发现还有一个死锁问题如下。

  如图2-3所示,主线程6860阻塞了,但是没说是因为m_cs也没说是因为SendMessage。我估摸了一下,猜测很可能就是因为::WaitForSingleObject。另外,工作线程6848因为SendMessage阻塞了。

  再通过加TRACE。运行,进一步查看TRACE结果,如图2-4所示。找到了主线程阻塞的位置如图2-2的174行,工作线程阻塞的位置如图2-1的79行。

  分析一下,一方面,主线程在174行阻塞的时候是持有CConsoleDialog的内核锁的(因为Stop是由SendMessage调用的,进一步来说,是按了CButton导致的主线程调用SendMessage),此时主线程阻塞在等待CEvent对象上,这个对象本来应该是由工作线程来Set的。

  另一方面,工作线程在79行阻塞是因为主线程持有了CConsoleDialog的内核锁。此时工作线程虽然持有CEvent,但因为阻塞在这里,所以没法去Set它。这下相互等待就发生了,从而导致了死锁。

  最后是把174-175行的代码放到了CConsoleDialog::~CConsoleDialog中才解决的。因为那两行代码主要是为了防止CConsoleDialog已经被销毁了工作线程还在往CConsoleDialog写数据导致的崩溃。所以说从功能上讲把那两行代码放到CConsoleDialog的析构函数中是没有问题的,同时也避免了调用::WaitForSingleObject的时候持有CConsoleDialog的SendMessage内核锁,就打破了死锁的闭环。

 

图2-1

 

图2-2

 

图2-3

 

图2-4

 

原文地址:https://www.cnblogs.com/qrlozte/p/6664235.html