线程死锁检测
死锁
首先,线程占用资源后会上锁,那么暂且将这个资源称为线程的绑定资源,当多个线程在绑定资源后希望获取其他线程的资源时,就有一个条件,就是相应的线程释放该资源(解锁),否则就要一直等待,而如果对应线程又在等待其他线程的绑定资源时且最终这个等待形成一个环的话,就会可能出现死锁的问题。
整体思路
使用dlsym劫持pthread_mutex锁操作,然后类似于装饰器模式添加一些额外的控制修饰代码;
dlsym
#define __GNU_SOURCE // 有些编译器可能需要加这个宏
#include <dlfcn.h>
g++ -ldl
线程和锁的关系
- 一个线程可以同一时刻拥有多个锁
- 一个锁同一时刻只能被一个线程所拥有
方案1 – 不可行
基于一个锁同一时刻只能被一个线程所拥有的属性,那么就可以构建一个mutex的容器,然后在上锁时进行添加,在解锁时进行移除;如果一个线程在需要锁资源且在容器中检测到锁被占用,就属于异常;
方法不可行,锁作为一个临界资源,当一个线程占用该资源时,且不够成死锁也会使上述情况。
方案2 – 不可行
通过图的概念,节点就是线程,边就是根据当前线程拥有的锁是否产生依赖而进行一个节点的连接,如果最终表示线程的节点构成一个环,那么说明发生死锁。
边:节点A指向节点B就是说线程A正在等待线程B释放资源;
共三个阶段:
- beforeLock:
检测锁是否已被占用,已被占用就添加边关系,表明依赖关系;对死锁检测( 环检测search_for_cycle() )也就是在这个位置进行;没有锁的话就直接获取到了锁资源。 - afterLock
在获取到锁资源后,对锁资源进行注册并管理;移除依赖关系; - afterunlock
对锁资源释放后,释放锁管理资源。
这个方案也不可行:
当两个线程(线程A和线程B)针对同一个锁进行了beforeLock阶段,且最终只有线程A获得了锁资源,此时图的拓扑结构应该是线程B依赖线程A(线程B在等待线程A释放锁资源),但是实际上图中并没有这个依赖关系。
方案3
方案2中在beforeLock阶段可能出现问题,核心就是完成beforeLock却并不代表对应线程获得了锁资源,因此引入图的一个锁对这个过程进行锁保护,但是又有另外一个问题:
lock(graphMutex);
if(judgeLocked(threadMutex))
{
addEdge();
wait(threadMutex);
}
else
{
addMutex();
lock(threadMutex);
}
unlock(graphMutex);
整个程序的伪代码如上,那么在判断锁资源无法可用的时候要添加图中的边,添加完毕之后要进行等待,此时如果直接阻塞等待锁资源,那么就极有可能出现死锁问题(不能在wait前释放锁graphMutex,那样可能出现的问题和方案2中的一样);因此需要将wait以pthread_mutex_lock非阻塞的方式进行实现。有两种解决方案:
- 第一种就是针对threadMutex创建一个条件变量,在wait发现没有获取到锁的时候就进行条件变量的线程让出;释放锁之后对该条件变量进行唤醒(条件变量的方式无法针对每个线程锁,因为使用条件变量必须在锁中才可以,这里还没有获取到锁资源);由于这个条件变量保护的是所有的图资源,因此需要使用条件变量的广播形式进行唤醒。
- 第二种就是直接死循环一直进行pthread_mutex_trylock,获取不到就直接进行线程切换 – sched_yield;
- sched_yield() causes the calling thread to relinquish the CPU. The thread is moved to the end of the queue for its static priority and a new thread gets to run.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~