C++死锁问题定位与分析

版权声明:本文为博主原创文章,如需转载请注明出处 https://blog.csdn.net/bajianxiaofendui/article/details/86983550

     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;
}

在这个例子中,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拥有,所以这两个线程陷入了互相等待的死锁中。等待锁的源码也直接定位到了,问题解决。

本文举例比较简单,主要是介绍死锁问题的定位以及分析的一般方法,具体问题还要视情况具体分析。

猜你喜欢

转载自blog.csdn.net/bajianxiaofendui/article/details/86983550