Windows进程死锁调试

本文探讨了一个三层死锁问题的解决过程,涉及测试代码、分析死锁线程、创建转储文件和Visual Studio调试。通过实例演示和关键教训,分享了避免和修复死锁的实用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

前段时间遇到一个三个锁死锁的问题,程序的部分业务是正常的,但是部分业务不能正常执行。最后还是通过转储文件,结合代码调试,才解决了问题。


一、测试代码

如下代码中是一个简化的死锁,实际情况中代码层层嵌套,有些变量因为是全局变量,所以对应的锁遍布整个代码等,使死锁问题更难找到头绪。

#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中查看相应线程就能清晰的看到哪些锁死锁。
在这里插入图片描述

总结

  1. 使用锁的地方尽可能不调用函数或接口。一层层的函数中也不确定哪个函数就无意中包含了和其它线程共用的锁,出现问题时,加大了代码排查的难度。
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;
}
  1. 缩小锁的使用范围。例如,用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);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值