死锁是四个条件同时满足时必然发生的现象;互斥条件需按需选用锁类型,占有并等待应避免嵌套加锁或用std::lock统一获取,循环等待须按地址顺序加锁,不可剥夺可用timed_mutex设超时。

死锁不是“可能出问题”,而是四个条件同时满足时必然发生——只要任一条件不成立,就不可能死锁。
互斥条件:资源不能被多个线程共享
比如 std::mutex 本质就是排他性的,同一时刻只允许一个线程持有。这不是 bug,是设计目的。强行“破坏”它(比如改用无锁结构)往往代价巨大,且不现实。
更实际的做法是:确认是否真需要互斥。如果是只读场景,优先用 std::shared_mutex;如果只是计数,考虑 std::atomic 替代锁。
占有并等待:线程拿着锁还去申请新锁
这是最常被忽视的触发点。典型错误是:
立即学习“C++免费学习笔记(深入)”;
- 函数 A 加了
mtx_a,调用函数 B,B 又去加mtx_b - 嵌套调用中未检查当前线程是否已持锁(
std::mutex不支持重入)
解决方法分两种场景:
— 若能预知所有需锁资源:用 std::defer_lock + std::lock 一次性获取多个锁,例如:std::lock(lock_a, lock_b),它内部会按地址顺序加锁,避免循环等待。
— 若无法预知(如回调、插件架构):改用 std::unique_lock<:recursive_mutex></:recursive_mutex>,但要注意这仅解决“同一线程重复加锁”,不解决多线程间交叉加锁问题。
循环等待:A 等 B,B 等 A
两个线程以不同顺序请求同一组锁,就构成环。编译器不会报错,运行时卡住也无明确提示。
必须统一加锁顺序,例如:
- 所有代码都先
lock(mtx_a)再lock(mtx_b) - 或按内存地址排序:
if (&mtx_a
别依赖变量名顺序(mutex_user 和 mutex_order 的字典序 ≠ 地址顺序),地址才是运行时真实依据。
不可剥夺:锁只能等持有者释放
标准 std::mutex 没有超时机制,一旦卡住就是永久阻塞。可用 std::timed_mutex 配合 try_lock_for 主动设限:
if (!mtx.try_lock_for(std::chrono::milliseconds(100))) {
// 处理获取失败:释放已有锁、回退、重试或报错
return false;
}
注意:try_lock_for 成功后仍要配对 unlock,否则下次调用会失败;且它不解决根本竞争,只是让问题“可观察、可恢复”。
真正难处理的是锁粒度与业务逻辑耦合过紧的情况——比如一个函数既更新用户余额又写日志,却用同一把锁保护两块无关资源。这时候拆锁比调顺序更重要,但得同步梳理数据一致性边界,容易被忽略。


















