死锁排查

在做这个实验之前,需要先构建数据。
首先是声明两个全局临界区和两个互斥对象

四个线程

全局变量初始化以及线程创建:

整个代码比较简单,就是4个线程,会进入临界区和互斥对象。由于进入顺序不一样,就会导致死锁。
编译好后,生成了EXE:deadlocks.exe

用windbg启动调试。

并观察屏幕输出。由于每个线程都会打印日志。如果不再打印日志了就证明死锁了。
(死锁的明显现象就是线程停止,处在等待状态。调入NtWaitForSingleObject等函数中不出来。所以当产生死锁时,CPU占用一定不高。)

楼主发现屏幕只打印了两条日志就停止打印了(由于死锁发生的时机跟线程调度有关,你在测试时可能输出的日志与楼主不同,死锁时堆栈也不同,不过排错的原理是一样的)。

看样子是锁住了。
按Ctrl+Break,进入调试状态

楼主要看都有哪些线程锁住了,查一下所有线程的调用栈

通过观察调用栈,发现如下信息
0号线程是主线程,正在等待用户输出,死锁和他没关系
1号线程在等待进入临界区
2号线程也在等待进入临界区
3号线程在等待一个对象,可能是互斥量
4号线程也在等待进入临界区
5号线程是调试线程,忽略之

问题只能出在 1,2,3,4号线程上面。
咱们逐一排查。先从一号线程开始
1号线程进入RtlEnterCriticalSection后处在等待状态,咱们看一下它等待的临界区当前属于哪个线程。临界区对象地址是00417150

临界区00417150属于0x00000bf8线程。咱们看一下0x00000bf8是几号线程

属于2号线程。
2号线程也在等待临界区,看看2号线程等待的临界区现在属于哪个线程

2号线程等待的临界区属于3号线程的。
那3号线程现在在等待什么那?通过堆栈咱们知道它调用了WaitForSingleObject,此函数第一个参数是等待的句柄。咱们看一下此句柄是什么类型

是一个互斥体对象。这个时候有一个麻烦,要想看到这个互斥体现在的所属线程在用户态是看不了的。天无绝人之路。咱们再开一个windbg,使用Local Kernel模式查看本地内核。并显示此互斥体句柄的详细信息

经过一顿寻找找到000007e8句柄互斥体现在的拥有者是线程ID=0bf8的线程。
好,内核调试器用完了,咱们再切换到用户态windbg,看看0bf8是几号线程

是2号线程!
当前基本上找到死锁的原因了。咱们来捋一下

1号线程在等待2号线程拥有的临界区对象
2号线程在等待3号线程拥有的临界区对象
3号线程在等待2号线程拥有的互斥体对象

2号线程与3号线程之间构成了一个锁循环,2号线程等待3号释放临界区对象,3号线程在等待2号线程释放互斥体对象。

好问题找到了。
那四号线程为啥也处在等待状态呢?咱们再看看4号线程在等待什么

四号线程也在等待2号线程。
由此说来是2号和3号线程死锁,导致1,4号同时在等待2号线程释放资源。

问题找到了,下一步就是解决了。
死锁的一般解决方法:

1. 避免一个线程同时拥有多个资源锁。
2. 在进入一个锁时,发现已经被其他线程拥有,则不进入等待状态,而是干别的事情,等一会再检查此锁是否被释放。这样可以避免无限等待。
临界区使用TryEnterCriticalSection 互斥体使用WaitForSingleObject(hMutex,0)探测锁是否被占用

发表评论

电子邮件地址不会被公开。 必填项已用*标注