C++死锁问题定位与分析
C++程序异常一般有两种表现形式:程序崩溃和程序无响应。程序崩溃主要是由指针,数组越界等原因引起,这种情况可以直接通过在程序中加入Dump捕捉逻辑,分析dump文件,定位出崩溃的代码。程序无响应主要是由死循环和死锁两个原因造成的,死循环我们可以通过查看CPU使用情况来初步判断,然后转存为Dump进行分析;死锁问题是C++中最难定位和分析的一种程序异常问题,下面我们主要介绍下死锁问题的一般定位和分析方法。
一,测试代码
在介绍死锁分析方法之前,先看一下本文中我们使用的测试代码:
CRITICAL_SECTION cs1;
CRITICAL_SECTION cs2;
DWORD WINAPI Thread1(LPVOID lpParameter);
DWORD WINAPI Thread2(LPVOID lpParameter);
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
//初始化关键代码段
InitializeCriticalSection(&cs1);
InitializeCriticalSection(&cs2);
//创建线程
HANDLE hThread1 = CreateThread(NULL, 0, Thread1, NULL, 0, NULL);
HANDLE hThread2 = CreateThread(NULL, 0, Thread2, NULL, 0, NULL);
//等待线程结束
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
Sleep(2000);
//关闭线程句柄
CloseHandle(hThread1);
CloseHandle(hThread2);
//释放关键代码段
DeleteCriticalSection(&cs1);
DeleteCriticalSection(&cs2);
return 0;
}
DWORD WINAPI Thread1(LPVOID lpParameter)
{
for (int i = 0; i < 10; i++)
{
EnterCriticalSection(&cs1);
Sleep(500);
EnterCriticalSection(&cs2);
std::cout << "Thread1" << std::endl;
LeaveCriticalSection(&cs2);
LeaveCriticalSection(&cs1);
}
return 1;
}
DWORD WINAPI Thread2(LPVOID lpParameter)
{
for (int i = 0; i < 10; i++)
{
EnterCriticalSection(&cs2);
Sleep(500);
EnterCriticalSection(&cs1);
std::cout << "Thread2" << std::endl;
LeaveCriticalSection(&cs1);
LeaveCriticalSection(&cs2);
}
return 1;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
在这个例子中,Thread1等待Thread2,Thread2等待Thread1,同时主线程等待着Thread1和Thread2,三个线程陷入死锁状态。
二,等待链
等待链是线程和同步对象的交替序列;每个线程都在等待它后面的对象,该对象由链中的后续线程拥有。通过分析等待链,我们可以清晰地看到各个子线程/子进程挂起所在等待的下一个线程/进程。(等待链更多知识请转至Wait Chain Traversal)
在《Windows核心编程》一书附带的源码09-LockCop中,详细介绍了WCT的编码实现,感兴趣的可以去阅读一下源码,附上下载链接(Windows核心编程(第5版中文版) 源码)。编译09-LockCop,我们可以得到09-LockCop.exe应用程序,打开改程序,选择我们的进程DeadLockTest.exe,分析如下:
线程8248等待线程9524,9524在等待6432,而6432又在等待9524,陷入了互相等待的处境中,导致死锁。因此可以确认DeadLockTest.exe的无响应是由死锁造成的。
我们也可以直接通过windows的任务管理器来检测一个程序是否陷入死锁,打开任务管理器-性能-资源监视器,选中我们的测试程序,右键菜单“分析等待链”:
分析结果如下:
可以看到9524线程与6432线程陷入了互相等待锁资源的处境,程序死锁。
三,死锁的定位和分析
如果死锁的程序刚好在我们自己的开发机器上,那么使用WinDbg的Attach To A Proccess功能将死锁程序直接附加到WinDbg中进行分析;如果不在我们的机器上,可以通过Windows的资源管理器对进行进行创建转储文件转换为dump(注意32位和64位的区别),再通过WinDbg对dump进行分析。为了方便,我们直接以Attach方式讲解。
Attach程序后,输入!locks命令查看锁的状态:
总共有两个锁cs1(012e4394)和cs2(012e437c),这两个锁的LockCount(表示还有多少个线程在等待这个临界区)都为1,说明这两个锁都处于等待状态。
再输入~*kb查看一下当前各个线程的堆栈:
线程2534和线程1920都处于等待状态,线程2534等待锁012e437c,线程1920等待锁012e4394。再结合上面的锁信息可以知道,锁012e4394被线程2534拥有,锁012e437c被线程1920拥有,所以这两个线程陷入了互相等待的死锁中。等待锁的源码也直接定位到了,问题解决。
本文举例比较简单,主要是介绍死锁问题的定位以及分析的一般方法,具体问题还要视情况具体分析。
from:https://blog.csdn.net/bajianxiaofendui/article/details/86983550