临界区:定位临界区(critical section)导致的死锁



在多线程开发中效率关系通常会选用CriticalSection作为同步机制初学者在设计开发多线程时经常会出现死锁情况昨天就看到有个哥们在发帖问这个(明显是郁闷中阿)这里通过个例子说下不用IntelThread CheckerThread Profiler也不用强大WinDbg只是用土土VC6自带调试器如何来轻松定位这种死锁问题


先贴下死锁例子代码:

/*******************************************************/
/* :DeadLock */
/* 功能:多线程环境中出现CriticalSection对象导致死锁 */
/* 作者:coding (http://blog.csdn.net/coding_hello) */
/* 日期:2008-12-09 */
/*******************************************************/
# <windows.h>
# <stdio.h>

CRITICAL_SECTION g_cs1;
CRITICAL_SECTION g_cs2;

DWORD WINAPI Worker(LPVOID lpParam)
{
(()lpParam 7)
{
while(1)
{
prf("Worker[%x]: 就我活着\n", GetCurrentThreadId);
Sleep(1000);
}
}

{
prf("Worker[%x]:准备进入临界段g_cs2\n", GetCurrentThreadId);
EnterCriticalSection(&g_cs2);
prf("Worker[%x]:成功进入临界段g_cs2\n", GetCurrentThreadId);

prf("Worker[%x]:准备进入临界段 g_cs1\n", GetCurrentThreadId);
EnterCriticalSection(&g_cs1);
prf("Worker[%x]:成功进入临界段 g_cs1\n", GetCurrentThreadId);
}
0;
}

( argc, char* argv)
{
InitializeCriticalSection(&g_cs1);
InitializeCriticalSection(&g_cs2);

prf("[%x]: 准备进入临界段 g_cs1\n", GetCurrentThreadId);
EnterCriticalSection(&g_cs1);
prf("[%x]: 成功进入临界段 g_cs1\n", GetCurrentThreadId);

HANDLE hThread[8];
for( i=0; i<8; i)
{
hThread[i] = CreateThread(NULL, 0, Worker, (LPVOID)i, 0, NULL);
}

prf("[%x]: 准备进入临界段 g_cs2\n", GetCurrentThreadId);
EnterCriticalSection(&g_cs2);
prf("[%x]: 成功进入临界段 g_cs2!\n", GetCurrentThreadId);

0;
}

首先编译上面把Project --〉Setting --〉C --〉 Catalog中选中Coding Generation然后在User run-time Library中选中Debug MultiThreaded按F7 build的然后F5直接调试运行可看到下面输出内容:

这里Worker是中创建16个线程里面是线程ID现在已经死锁了没有退出只有个线程还在每秒钟输出就是这种效果现在进入我们主题把死锁原因定位出来(当然这里例子很简单就能看出来但是实际情况往往要复杂得多而分析定位思路方法是不变)

暂停运行可以通过Debug工具栏里那个表示暂停小按钮(哪怕你第次用VC6只要用过随身听录音机mp3vcddvd都认识那个钮不认识打pp)不知道如何找到Debug工具栏去看看VC员指南或者在菜单里面Debug --〉Break也行

第 2步暂停下来的后通常看到是满屏汇编代码不要慌把Call Stack窗口打开(按快捷键Alt + 7或者是通过菜单View --〉Debug Window --〉Call Stack来激活)大概会看到下面内容:

这里看到是当前线程堆栈不出意外这个线程应该是那个还活着不停输出线程(没想清楚再复习下操作系统中线程调度内容推荐Windows Internals)实际上从上面选中行Worker(void * 00000007)中就看到了这就是i=7时那个线程双击这行进去看看如下图:

来看看

这几个圈起来内容个是lpParam我们看到它值是7正是我们创建第8个线程它在这不停循环输出着第 2个圈中内容是g_cs2第 3个是g_cs1

第 3步:看看两个全局变量CriticalSection我们看看目前是什么状况分别QuickWatch这两个变量

时间比较紧下最关心字段OwningThread我们看到g_cs1OwningThread是0x3f18g_cs2OwningThread是0x36b4这表示CriticalSection g_cs1被线程0x3f18占着而g_cs2被线程0x36b4占着

第 4步:看看占着这两个临界区线程在干什么选中菜单Debug --〉Thread看到下面输出:

我们看到前面蓝色背景最开头有个'*'号这表示是调试器当前线程前面7640是线程ID后面[Worker]是当前运行到位置我们关心是0x3f18和0x36b4这两个线程往上看就有了0x3f18就是线程0x36b4是个Worker线程先看线程在0x3f18那行双击很可能又是满屏汇编

第 5步:跟第 2步样操作从Call Stack中找到我们代码位置然后就发现代码停在下面这行上:

EnterCriticalSection(&g_cs2);

介绍说明什么呢?介绍说明线程在等待进入临界区g_cs2刚才已经提到线程已经进入了g_cs1而g_cs2被线程0x36b4占着所以如果g_cs2不被释放就会直占着g_cs1不放死等g_cs2重复第 4 5步看看0x36b4在干吗我们看到它也停在类似代码行上:

EnterCriticalSection(&g_cs1);

很明显了这个线程已经进入了g_cs2但是还要g_cs1;g_cs1又被占着也非要进g_cs2这就形成了死锁条件:各自占有循环等待

第 6步:解决问题弄清楚了死锁原因剩下就是破坏死锁条件了依赖于业务解决吧

如何样这个土土VC6调试器还是很有用吧当然尽管本文是以VC6作为环境其他开发环境应该也有类似功能基本思路就是确认被死锁线程或者被死锁变量这里例子是先看到没死锁线程刚好能看到那两个g_cs1,2所以就能确认是哪俩线程有问题实际情况通常是根据业务大致确认死锁线程然后看看是哪几个线程占着哪几个CriticalSection对象在循环等待最后把关系理清楚去掉耦合性来解决死锁



时间比较紧可能有地方写得还不清楚不到位比如CriticalSection数据结构中其他几个字段以后有机会再说吧赶紧吃饭上班去了 大家好胃口88~

Tags:  临界区对象 什么是临界区 vc临界区 临界区

延伸阅读

最新评论

发表评论