std::mutex必须显式调用lock()和unlock()才生效,推荐用std::lock_guard自动管理;多锁用std::lock防死锁;避免滥用std::recursive_mutex;勿手写spinlock;临界区应尽量小且精准。

std::mutex 要加 lock() 才真正起作用
很多人写完 std::mutex 就以为线程安全了,结果还是出竞态——因为没调用 lock() 或没配对 unlock()。C++ 的 std::mutex 是“哑巴锁”,不自动介入任何代码段,全靠你手动控制临界区边界。
常见错误现象:std::terminate 被抛出(重复 lock() 同一线程)、数据错乱(忘记 lock())、死锁(lock()/unlock() 不成对或顺序混乱)。
- 必须在访问共享变量前调用
mtx.lock(),访问完立刻mtx.unlock() - 更推荐用
std::lock_guard自动管理:它在构造时加锁、析构时解锁,哪怕提前 return 或异常也能保底释放 - 不要跨函数传递裸
std::mutex对象;若需共享,传引用或指针,但得确保生命周期长于所有使用者
std::mutex mtx;
int counter = 0;
<p>void increment() {
std::lock_guard<std::mutex> guard(mtx); // 自动加锁/解锁
++counter;
}多个 mutex 一起用时,std::lock 比挨个 lock() 安全
当一段逻辑要同时锁两个资源(比如两个容器、两个文件句柄),如果按固定顺序分别调用 mtx1.lock(); mtx2.lock();,一旦不同线程按相反顺序加锁,就可能死锁。
使用场景:实现线程安全的“转账”操作(从 A 扣款、向 B 加款),必须同时锁定账户 A 和 B 的互斥量。
立即学习“C++免费学习笔记(深入)”;
- 改用
std::lock(mtx1, mtx2)—— 它内部用无死锁算法一次性获取多个锁 - 配合
std::lock_guard的带std::defer_lock构造函数,避免重复加锁 - 切勿混用:不能对已由
std::lock锁住的 mutex 再单独调用lock()
std::mutex mtx1, mtx2;
void transfer(int& from, int& to, int amount) {
std::lock(mtx1, mtx2); // 原子性获取两把锁
std::lock_guard<std::mutex> g1(mtx1, std::defer_lock);
std::lock_guard<std::mutex> g2(mtx2, std::defer_lock);
from -= amount;
to += amount;
}std::recursive_mutex 不等于“随便嵌套”,它有开销且掩盖设计问题
看到“函数里调自己、又需要锁”,第一反应是换 std::recursive_mutex?先停一下。它允许同一线程多次 lock(),但每次 lock() 都要计数、每次 unlock() 都要减一,性能比 std::mutex 低 2–5 倍(取决于实现),而且容易掩盖本该拆分的逻辑。
使用场景有限:确实无法重构的递归算法 + 共享状态更新(如树遍历中边走边改节点标记);或遗留 C 接口回调导致不可控重入。
- 除非明确需要重入,否则坚持用
std::mutex+std::lock_guard - 若用了
std::recursive_mutex,务必保证lock()/unlock()次数严格匹配,漏一次就永远卡住 - 调试时注意:gdb / lldb 看不到递归锁的当前持有深度,只能靠日志或自定义 wrapper
std::mutex 在 Windows 上默认是轻量级内核对象,Linux 上依赖 futex,别手写 spinlock
有人为“避免系统调用开销”想自己实现忙等锁(spinlock),这是危险的优化。现代 std::mutex 实现已经做了分层:短等待用用户态自旋,长等待才进内核挂起,平衡了延迟和 CPU 占用。
性能影响明显:在高争用、低延迟敏感场景(如高频交易 tick 处理),std::mutex 可能不如 std::atomic + CAS;但在绝大多数业务逻辑中,它足够快且更安全。
- 别用
while(!mtx.try_lock()) { /* spin */ }替代标准锁——没有退避策略会烧满 CPU - 确认编译器和 STL 版本:旧版 libstdc++(GCC 4.8 以前)在 Linux 上不支持 futex,实际是 pthread_mutex_t 的封装,性能较差
- 跨平台项目注意:Windows 上
std::mutex底层是 CRITICAL_SECTION(用户态优先),Linux 上是 pthread_mutex_t(futex 优化),行为一致但 trace 工具看到的系统调用不同
真正难的不是怎么加锁,而是判断哪里该加、加多细、加多久。很多 bug 出在“以为这里没竞争”,或者“锁了整个函数却只改了一行”。临界区越小越好,但也不能小到让逻辑断裂——这中间的分寸,得靠日志、压测和反复看堆栈来校准。









