C++如何判断一个程序是 死锁 还是 死循环,如何进行问题定位与分析

目录

一、初步诊断:

1、死循环:

2、死锁:

二、进一步定位分析:

1、初步诊断为 死循环 时,可转存为 Dump 进行分析

2、死锁 情况就比较复杂:

3、死锁的定位和分析

4、如何转储 dump [自己监控异常; 另起进程 监控异常]

三、、代码与图示:

1、死循环:

2、死锁:


一、初步诊断:

通过 ‘任务管理器’ 查看软件的状态 与 CPU 使用情况

1、死循环:

软件状态:未响应

CPU:一直保持非0,处于活跃状态

原理:

如果主线程出现死循环,那么windows将不能从消息队列中取出消息,并进行处理,所以出现卡死现象。为了验证是这个原因导致界面卡死,打开任务管理器,如果该进程的cpu使用率一直保持非零,比如一直保持在 12%,那么界面卡死的原因是主线程死循环了。

2、死锁:

软件状态:正在运行

CPU:进程的 cpu 使用率一般是0

原理:

如果主线程由于跟其他的线程由于争夺资源或者锁,出现了死锁,那么主线程会一直等待资源或者锁,导致主线程不能继续往下执行,分发和处理消息,所以出现卡死

 

二、进一步定位分析:

1、初步诊断为 死循环 时,可转存为 Dump 进行分析

 

2、死锁 情况就比较复杂:

--- 使用 “任务管理器” 来检测一个程序是否陷入死锁

--- 打开 【任务管理器 - 详细信息 - 分析等待链】

--- 分析结果如下,可以看到当前测试程序处于死锁状态

--- 最后,对其进行【创建转储文件】转换为dump,再通过WinDbg对dump进行分析

3、死锁的定位和分析

如果死锁的程序刚好在我们自己的开发机器上,那么使用 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拥有,所以这两个线程陷入了互相等待的死锁中。

等待锁的源码也直接定位到了,问题解决。

4、如何转储 dump [自己监控异常; 另起进程 监控异常]

A>、自己监控异常

MS 为我们提供了一个dbghelp.dll 的动态库

通过其提供的 MiniDumpWriteDump方法,我们可以进行dump的转储。

什么时候转储 dump 呢?

这里来介绍一下SetUnhandledExceptionFilter这个函数,当异常没有处理的时候,系统就会调用 SetUnhandledExceptionFilter 所设置异常处理函数,通过传入的回调函数来进行一些处理。

那么这个时候,我们就可以在回调函数中对当前程序的进程信息转储dump了。

缺陷:

这种方式有一些无能为力的时候,如果程序自身出现了无响应,整个进程卡死了,或者是程序的主线程中出现了死循环,以及程序在运行时闪退了

这些时候,程序自身是很难处理所出现的异常的,或者是根本没来得及处理

解决方法:另起进程来监控异常

B>、另起进程 监控异常

如果使用一个另外的程序来监测一个指定的程序,在其出现异常的时候,做一些后续的处理,那么首先面临的两个问题是:

问题一:用于监测的程序如何知道被监测的程序出现异常了;
问题二:当被监测的程序出现异常之后该如何处理。

问题一的解决方法:

Win32 有两个 API 函数可以帮助我们解决问题一,它们分别是 IsHungThread 和 IsHungAppWindow

它们可以根据所传入的进程句柄,来判断当前的进程是否处于正常响应的状态。

当被监测的程序出现了非正常响应,且满足一定的条件之后,我们可以认为它是出现了异常了,或者说不是一个理想的状态下了。

问题二的解决方法:

处理的目标同样是提示用户程序出现了异常,同时生成dump文件

MS为我们提供的另外一个工具:procdump,这个工具可以通过命令行来调用,比如对指定的进程进行dump转储,可以使用以下命令:

-ma 是转储参数,表示是以什么模式来进行转储。 

ma 是全量转储,即将所有进程相关的信息都进行转储。 

mp 是只转储有用的信息。具体介绍可以参考msdn介绍文档。 

8978 是对应进程的进程id。 

Test.dmp 是转储后dump文件的名称。

所以当我们知道被监测的程序出现异常之后,可以通过在程序中调用这个工具来对其进行dump的转储。

详情请查看: https://rdc.hundsun.com/portal/article/dump-607.html

扩展阅读:https://www.cnblogs.com/xybaby/p/8025435.html

三、、代码与图示:

1、死循环:

while (1){}

2、死锁:

#include <windows.h>
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
using namespace std;

CRITICAL_SECTION cs1;
CRITICAL_SECTION cs2;

DWORD WINAPI Thread1(LPVOID lpParameter);
DWORD WINAPI Thread2(LPVOID lpParameter);

int main(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;
}

 

 

  • 7
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C++ 死锁是多线程编程常见的问题。以下是一些快速定位死锁的方法: 1. 观察程序行为:当程序出现死锁时,观察是否存在多个线程互相等待对方持有的资源的情况。这种相互等待的循环便是典型的死锁现象。 2. 使用调试工具:使用调试工具可以帮助您定位死锁。例如,GDB、Visual Studio 的调试器等可以帮助您在程序执行过程观察线程的状态和资源的占用情况。 3. 分析资源竞争:检查代码是否存在对共享资源(如互斥量、信号量、锁等)的并发访问。确保在访问共享资源时正确使用同步机制,例如使用互斥量进行保护和同步。 4. 检查资源释放:检查代码是否存在未正确释放资源的情况。确保在使用完资源后及时释放,以避免造成资源泄漏和死锁。 5. 使用工具进行静态分析:静态分析工具可以帮助检测代码潜在的死锁问题。例如,Clang 提供了一些工具(如 ThreadSanitizer)来检查并发程序的数据竞争和死锁。 6. 使用线程安全的数据结构:使用线程安全的数据结构和算法可以减少死锁的潜在风险。例如,C++11 标准库提供了一些线程安全的容器和算法。 请注意,死锁问题可能比较复杂,定位和解决死锁需要仔细分析和调试。以上方法提供了一些常见的定位死锁的方法,但具体情况还需根据实际代码和环境进行分析

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值