死锁原因
- 不可重入/重复加锁:同一线程对同一个非可重入锁(如普通 std::mutex)连续进行多次加锁操作,导致自身在第二次加锁时被永久阻塞。
- 锁未释放/遗漏解锁:线程1忘记解锁了,导致线程2无法上锁,也会进入死锁
- 线程非安全终止(锁被永不释放):如果一个线程上锁,操作临界资源;当它在操作临界资源期间,被其它线程取消掉;此时被取消的线程没有解锁,导致其它的线程无法上锁操作临界资源;导致死锁的发生
- 多锁顺序不一致(最常见):线程 A 先锁锁1再锁锁2;线程 B 先锁锁2再锁锁3,互相死等。
- 在持有锁的时候调用未知回调/虚函数:在锁的保护区内调用了外部传入的函数,而那个函数内部又试图去拿同一把锁(或者另一把被占用的锁)。
- 信号处理函数中加锁:主线程持有锁时被信号打断,信号处理函数又去获取这把锁。
死锁四大必要条件
- 互斥条件 (Mutual Exclusion):资源具有排他性,同一时刻只能被一个线程持有(如“独占玩具”)。
- 请求与保持条件 (Hold and Wait):线程已持有一部分资源,同时又在等待获取其他被占用的资源(如“吃着碗里的,看着锅里的”)。
- 不可剥夺条件 (No Preemption):已被线程获取的资源在未主动释放前,其他线程不能强行抢占(如“不能硬拽”)。
- 循环等待条件 (Circular Wait):存在一个线程资源的环形等待链,每个线程都在等待下一个线程释放资源(如“环形死锁”)。
互斥条件
- 英文:Mutual Exclusion
- 比喻:独占玩具
- 白话解释:一把锁/一个资源在同一时刻只能被一个人占用
- 说明:锁在同一时刻只能被一个线程持有,如果线程 A 拿着,线程 B 就必须排队,不能两个人同时拿。
请求与保持条件
- 英文:Hold and Wait
- 比喻:吃着碗里的,看着锅里的
- 白话解释:一个线程已经持有一把锁),同时又去申请另一把锁
- 场景:线程 A 手里拿着“积木”不放,同时开口要“画笔”;而“画笔”现在在别人手里。
不可剥夺条件
- 英文:No Preemption
- 白话解释:别人手里拿到的资源,在人家没主动用完放下之前,你不能强行从人家手里夺过来。
- 场景:线程 B 拿着“画笔”,线程 A 再急也不能去强行把“画笔”硬拽过来,只能傻傻等待线程 B 主动还回来。
循环等待条件
- 英文:Circular Wait
- 白话:环形等待链
- 场景:
- 线程 A 拿着“积木”,在等线程 B 手里的“画笔”;
- 线程 B 拿着“画笔”,在等线程 A 手里的“积木”;
- 两个人都在等对方手中的东西,谁也不肯先放手,于是永久卡死。
死锁解决与防护
- 线程取消清理例程:在 POSIX 线程中,利用 pthread_cleanup_push 与 pthread_cleanup_pop 注册解锁例程,确保线程被意外取消时自动释放锁。
- RAII 自动管理锁:在 C++ 中通过 std::lock_guard、std::unique_lock 或 std::scoped_lock 管理锁生命周期,利用析构函数保证异常安全与自动解锁。
- 死锁预防策略:严格保持多锁获取顺序一致;使用 C++17 的 std::scoped_lock 同时锁定多个互斥量(内部采用 Deadlock-avoidance 算法);采用 try_lock 带超时机制的加锁。
线程取消机制
- 线程如果被意外取消,需要使用线程取消机制
//线程的取消例程函数(很重要的作用防止被以外取消了 可以解锁)
void routine(void *arg)
{
printf("突然被取消了 马上解锁\\n");
pthread_mutex_unlock(&m);
}
void *start_routine1(void *arg)
{
//1.提前准备一个解锁处理函数,并将其压入线程专用的函数栈中备用。
pthread_cleanup_push(routine,NULL);
//2.准备操作临界资源,加锁
pthread_mutex_lock(&m);
//3.操作临界资源
//如果在我10秒之内突然被取消(需要在线程取消例程函数中解锁)
int cnt = 10;
g_val = 100;
while (cnt—)
{
printf("start_routine1 g_val:%d\\n", g_val++);
sleep(1);
}
//4. 解锁
// pthread_mutex_unlock(&m);
//5.在函数栈中弹出处理函数。
pthread_cleanup_pop(1);//0不执行 非0才执行
pthread_exit(NULL);
}
网硕互联帮助中心




评论前必须登录!
注册