提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
前段时间遇到一个三个锁死锁的问题,程序的部分业务是正常的,但是部分业务不能正常执行。最后还是通过转储文件,结合代码调试,才解决了问题。
一、测试代码
如下代码中是一个简化的死锁,实际情况中代码层层嵌套,有些变量因为是全局变量,所以对应的锁遍布整个代码等,使死锁问题更难找到头绪。
#include <windows.h>
#include <stdio.h>
CRITICAL_SECTION cs1;
CRITICAL_SECTION cs2;
CRITICAL_SECTION cs3;
int g_iTest1 = 0;
int g_iTest2 = 0;
int g_iTest3 = 0;
DWORD WINAPI ThreadProc1(LPVOID lpParameter)
{
while (true)
{
EnterCriticalSection(&cs1);
g_iTest1 = 1;
Sleep(10);
EnterCriticalSection(&cs2);
printf("g_iTest2=%d\n");
LeaveCriticalSection(&cs2);
LeaveCriticalSection(&cs1);
}
return 0;
}
DWORD WINAPI ThreadProc2(LPVOID lpParameter)
{
while (true)
{
EnterCriticalSection(&cs2);
g_iTest2 = 1;
Sleep(10);
EnterCriticalSection(&cs3);
printf("g_iTest3=%d\n");
LeaveCriticalSection(&cs3);
LeaveCriticalSection(&cs2);
}
return 0;
}
DWORD WINAPI ThreadProc3(LPVOID lpParameter)
{
while (true)
{
EnterCriticalSection(&cs3);
g_iTest3 = 1;
Sleep(10);
EnterCriticalSection(&cs1);
printf("g_iTest1=%d\n");
LeaveCriticalSection(&cs1);
LeaveCriticalSection(&cs3);
}
return 0;
}
int main(int argc, TCHAR* argv[], TCHAR* envp[])
{
InitializeCriticalSection(&cs1);
InitializeCriticalSection(&cs2);
InitializeCriticalSection(&cs3);
HANDLE hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
HANDLE hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
HANDLE hThread3 = CreateThread(NULL, 0, ThreadProc3, NULL, 0, NULL);
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
WaitForSingleObject(hThread3, INFINITE);
CloseHandle(hThread1);
CloseHandle(hThread2);
CloseHandle(hThread3);
DeleteCriticalSection(&cs1);
DeleteCriticalSection(&cs2);
DeleteCriticalSection(&cs3);
return 0;
}
二、分析和调试
1.查看死锁线程
任务管理器中分析等待链能看到程序是哪些线程死锁。
2.创建转储文件
3.VS调试
根据分析等待链中死锁线程,在VS中查看相应线程就能清晰的看到哪些锁死锁。
总结
- 使用锁的地方尽可能不调用函数或接口。一层层的函数中也不确定哪个函数就无意中包含了和其它线程共用的锁,出现问题时,加大了代码排查的难度。
void FunN()
{
EnterCriticalSection(&cs2);
//...
LeaveCriticalSection(&cs2);
}
void Fun2()
{
FunN();
}
void Fun1()
{
Fun2();
}
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
EnterCriticalSection(&cs1);
/*
* Fun1层层调用,最终调用到了FunN。也许FunN最开始编写出来的时候并不是给ThreadProc的功能使用的,
* ThreadProc最开始的时候也没有使用到FunN。后面不断的开发中,无意使用到了FunN,才导致了死锁问题。
*/
Fun1();
LeaveCriticalSection(&cs1);
return 0;
}
- 缩小锁的使用范围。例如,用do…while限制锁的使用范围
//有时候不小心可能这么写,当然一般来说,如果没有死锁的情况的话这样写也没有问题
void Fun1()
{
EnterCriticalSection(&cs1);
g_iTest1 = 1;
EnterCriticalSection(&cs2);
g_iTest2 = g_iTest1;
LeaveCriticalSection(&cs2);
LeaveCriticalSection(&cs1);
}
//do...while缩小锁的范围
void Fun1()
{
int iTemp = 0;
do
{
EnterCriticalSection(&cs1);
g_iTest1 = 1;
iTemp = g_iTest1;
LeaveCriticalSection(&cs1);
} while (0);
do
{
EnterCriticalSection(&cs2);
g_iTest2 = iTemp;
LeaveCriticalSection(&cs2);
} while (0);
}