在多线程程序的开发中,因为效率的关系,通常会选用CriticalSection作为同步的机制。初学者在设计开发多线程程序时经常会出现死锁的情况,昨天就看到有个哥们在发帖问这个(明显是郁闷中阿)。这里通过一个例子说下不用Intel的Thread Checker,Thread Profiler,也不用强大的WinDbg,只是用土土的VC6自带的调试器如何来轻松的定位这种死锁问题。
先贴下死锁例子的代码:
-
-
-
-
-
-
- #include<windows.h>
- #include<stdio.h>
-
- CRITICAL_SECTIONg_cs1;
- CRITICAL_SECTIONg_cs2;
-
- DWORDWINAPIWorker(LPVOIDlpParam)
- {
-
if((int)lpParam==7)
- {
-
while(1)
- {
-
printf("Worker[%x]:就我活着/n",GetCurrentThreadId());
- Sleep(1000);
- }
- }
-
else
- {
-
printf("Worker[%x]:准备进入临界段g_cs2/n",GetCurrentThreadId());
- EnterCriticalSection(&g_cs2);
-
printf("Worker[%x]:成功进入临界段g_cs2/n",GetCurrentThreadId());
-
printf("Worker[%x]:准备进入临界段g_cs1/n",GetCurrentThreadId());
- EnterCriticalSection(&g_cs1);
-
printf("Worker[%x]:成功进入临界段g_cs1/n",GetCurrentThreadId());
- }
-
return0;
- }
-
-
intmain(intargc,char*argv[])
- {
- InitializeCriticalSection(&g_cs1);
- InitializeCriticalSection(&g_cs2);
-
-
printf("main[%x]:准备进入临界段g_cs1/n",GetCurrentThreadId());
- EnterCriticalSection(&g_cs1);
-
printf("main[%x]:成功进入临界段g_cs1/n",GetCurrentThreadId());
-
- HANDLEhThread[8];
-
for(inti=0;i<8;i++)
- {
- hThread[i]=CreateThread(NULL,0,Worker,(LPVOID)i,0,NULL);
- }
-
-
printf("main[%x]:准备进入临界段g_cs2/n",GetCurrentThreadId());
- EnterCriticalSection(&g_cs2);
-
printf("main[%x]:成功进入临界段g_cs2!/n",GetCurrentThreadId());
-
-
return0;
- }
-
-
首先编译上面的程序。把Project --〉Setting --〉C++--〉 Catalog中选中Coding Generation,然后在User run-time Library中选中Debug MultiThreaded。按F7 build之,然后F5直接调试运行。可看到下面的输出内容:
这里的Worker是main中创建的16个线程,[]里面的是线程的ID。现在程序已经死锁了,没有退出,只有一个线程还在每秒钟输出一次。要的就是这种效果,现在进入我们的主题。把死锁的原因定位出来(当然,这里的例子很简单就能看出来,但是实际的情况往往要复杂得多,而分析定位的方法是不变的)。
第一步,暂停程序的运行。可以通过Debug工具栏里那个表示暂停的小按钮(哪怕你第一次用VC6,只要用过随身听,录音机,mp3,vcd,dvd的都认识那个钮,不认识的打pp)。不知道怎么找到Debug工具栏的,去看看VC的程序员指南。或者在菜单里面的Debug --〉Break也行。
第二步,暂停下来之后,通常看到的是满屏的汇编代码。不要慌,把Call Stack窗口打开(按快捷键Alt + 7,或者是通过菜单View --〉Debug Window --〉Call Stack来激活),大概会看到下面的内容:
这里看到的是当前线程的调用堆栈。不出意外的话,这个线程应该是那个还活着不停输出的线程(没想清楚的再复习一下操作系统中线程调度的内容,推荐Windows Internals)。实际上,从上面选中的那一行Worker(void * 00000007。。。)中就看到了这就是i=7时的那个线程。双击这一行进去看看,如下图:
来看看
这几个圈起来的内容。第一个是lpParam,我们看到它的值是7,正是我们创建的第8个线程。它在这不停的循环输出着。第二个圈中的内容是g_cs2,第三个是g_cs1。
第三步:看看两个全局变量CriticalSection我们看看目前是什么状况。分别QuickWatch这两个变量。
时间比较紧,说一下最关心的字段OwningThread。我们看到g_cs1的OwningThread是0x3f18,g_cs2的OwningThread是0x36b4。这表示CriticalSection g_cs1被线程0x3f18占着,而g_cs2被线程0x36b4占着。
第四步:看看占着这两个临界区的线程在干什么。选中菜单Debug --〉Thread,看到下面的输出:
我们看到前面蓝色背景的一行,最开头有个'*'号,这表示是调试器的当前线程。前面的7640是线程的ID,后面的[Worker]是当前运行到的位置。我们关心的是0x3f18和0x36b4这两个线程,往上一看就有了。0x3f18就是main线程,0x36b4是一个Worker线程。先看一下main线程,在0x3f18那行双击一下。很可能又是满屏的汇编。
第五步:跟第二步一样操作,从Call Stack中找到我们代码的位置。然后就发现代码停在下面这行上:
EnterCriticalSection(&g_cs2);
说明什么呢?说明main线程在等待进入临界区g_cs2。刚才已经提到main线程已经进入了g_cs1,而g_cs2被线程0x36b4占着,所以如果g_cs2不被释放的话,main就会一直占着g_cs1不放,死等g_cs2。重复第四,五步看看0x36b4在干吗,我们看到它也停在类似代码行上:
EnterCriticalSection(&g_cs1);
嗯,很明显了。这个线程已经进入了g_cs2,但是还要g_cs1;g_cs1又被main占着,main也非要进g_cs2,这就形成了死锁的条件:各自占有,循环等待。
第六步:解决问题。弄清楚了死锁的原因,剩下的就是破坏死锁的条件了,依赖于业务解决吧。
怎么样,这个土土的VC6调试器还是很有用吧。当然,尽管本文是以VC6作为环境,其他的开发环境应该也有类似的功能。基本的思路就是确认被死锁的线程或者被死锁的变量。这里的例子是先看到的没死锁的线程,刚好能看到那两个g_cs1,2的值,所以就能确认是哪俩线程有问题。实际的情况通常是根据业务大致确认死锁的线程,然后看看是哪几个线程占着哪几个CriticalSection对象在循环等待,最后把关系理清楚,去掉耦合性来解决死锁。
时间比较紧,可能有的地方写得还不清楚,不到位,比如CriticalSection数据结构中的其他几个字段,以后有机会再说吧,赶紧吃饭,上班去了。 大家好胃口,88~
相关推荐
银行家算法是一种最有代表性的避免死锁的一种算法,在避免死锁的方法中允许进程动态的申请资源。
操作系统、死锁检测、 在VC++6.0环境下编译通过,直接可以运行
操作系统:第3章进程调度与死锁1.ppt
操作系统:第3章进程调度与死锁3.ppt
操作系统:第3章进程调度与死锁2.ppt
操作系统课设:仿真模拟银行家算法对死锁的避免 文档报告 代码.doc
操作系统(OS, Operating System) 课件:第3章进程调度与死锁.pdf
银行家算法是一种最有代表性的避免死锁的算法。在避免死锁方法中允许进程动态地申请资源,但系 银行家算法 统在进行资源分配之前,应先计算此次分配资源的安全性,若分配不会导致系统进入不安全状态,则分配,否则...
关于在类的构造函数和析构使用临界区函数导致的多线程死锁的一个经验之谈
oracle数据库解决死锁,使用plsql语句手动解决死锁问题
实验二:死锁的检测和预防.doc
有关表死锁的详细图片 博文链接:https://meteor-1988.iteye.com/blog/1568695
操作系统之银行家算法:从原理到实现的死锁避免教程 操作系统之银行家算法:从原理到实现的死锁避免教程 操作系统之银行家算法:从原理到实现的死锁避免教程 操作系统之银行家算法:从原理到实现的死锁避免教程 ...
模拟一个没有死锁的哲学家进餐问题,用VC++6.0设计的
用VC实现两个线程的同步,申请不当则可能会发会死锁. 此例就是如此
操作系统课程 银行家算法用标准C++实现,可以顺利运行于vs2008 vc6.0 code::block。 算法作用是预防进程的死锁状态,核心是验证分配是否存在安全序列.
目的管道有助于“管道” .NET中的流,而不会导致死锁。要求.NET 4.5及更高版本安装Pipe在上可用,并且可以使用VisualStudio NuGet程序包管理器或通过NuGet命令行作为程序包安装: 安装包管道用法using Narkhedegs ; ...
计算机硬件与软件:2.2.5 处理机管理_死锁.ppt
第一篇:JAVA 基础那点破事!反射、泛型、IO模型、重载、非阻塞 第二篇:JAVA 集合那点破事!集合、扩容、数组、链表 第三篇:JAVA 并发!JUC、死锁、CAS、线程池 第四篇:JVM 那点破事!内存结构、垃圾收集、OOM、...