std::mutex不可拷贝,须传引用或指针;应优先用std::lock_guard自动管理锁,多锁用std::lock防死锁;非递归,需递归语义时改用std::recursive_mutex。

std::mutex 不能直接拷贝,必须用引用或指针传递
很多初学者在把 std::mutex 当作函数参数传入时,下意识写成值传递,结果编译报错:use of deleted function ‘std::mutex::mutex(const std::mutex&)’。这是因为 std::mutex 的拷贝构造函数被显式删除了——它天生就不支持复制。
正确做法是传引用(std::mutex&)或指针(std::mutex*),常见于封装加锁逻辑的辅助函数中:
void safe_increment(int& counter, std::mutex& mtx) {
mtx.lock();
++counter;
mtx.unlock();
}
- 别写
void f(std::mutex m)—— 编译不过 - 避免裸调
lock()/unlock(),容易忘掉 unlock 或异常后泄露锁 - 优先用
std::lock_guard或std::unique_lock自动管理生命周期
std::lock_guard 是最简安全选择,但不支持手动解锁或超时
如果你只需要“进作用域加锁、出作用域自动释放”,std::lock_guard 就够了。它轻量、无状态、构造即加锁、析构即释放,且不支持转移或手动控制,反而保证了安全性。
典型误用是试图在作用域内提前释放锁:guard.unlock() —— 这个函数根本不存在。一旦你有这种需求,说明该换 std::unique_lock 了。
立即学习“C++免费学习笔记(深入)”;
-
std::lock_guard构造时阻塞等待,不支持超时(比如等 100ms 后放弃) - 不能用于条件变量的 wait 操作(需要可转移、可手动释放的锁)
- 多线程共享一个
std::mutex实例时,所有std::lock_guard必须绑定到它,不能混用不同 mutex
多个 mutex 加锁顺序不一致 → 死锁高发区
当一段逻辑要同时锁两个资源(比如两个 std::mutex 成员),如果线程 A 先锁 mtx_a 再锁 mtx_b,而线程 B 反过来先锁 mtx_b 再锁 mtx_a,就极易死锁。
C++ 标准库提供了 std::lock 函数来规避这个问题:它能原子性地同时锁定多个 mutex,内部使用避免死锁的算法(如按地址排序)。
std::mutex mtx_a, mtx_b; std::lock(mtx_a, mtx_b); // 安全,不会死锁 std::lock_guard<std::mutex> guard_a(mtx_a, std::defer_lock); std::lock_guard<std::mutex> guard_b(mtx_b, std::defer_lock); std::lock(guard_a, guard_b); // 等价写法
- 永远不要靠“约定”来保证加锁顺序;代码演进后很容易被破坏
-
std::lock要配合std::defer_lock使用,否则构造std::lock_guard时会立刻尝试加锁,失去原子性 - 若需对多个 mutex 做 try_lock,用
std::try_lock,返回 -1 表示失败
std::mutex 不可重入,递归加锁会 crash
std::mutex 是非递归锁。同一个线程重复调用 lock()(哪怕在不同函数层级),行为是未定义的——Linux 下通常直接 abort,Windows 下可能死锁或崩溃。
如果你确实需要递归语义(比如类成员函数互相调用,都带锁),别硬改逻辑绕开,直接换 std::recursive_mutex。但它更重、性能略差、且容易掩盖设计问题。
- 绝大多数场景不需要递归锁;出现需求,先检查是否可以把临界区拆得更细,或用 RAII 封装更清晰
-
std::recursive_mutex的lock()/unlock()必须严格配对,次数也要一致,否则仍会出问题 - 调试时注意:gdb 可能无法准确显示哪个线程卡在哪个 mutex 上,建议用
std::mutex+ 日志 + 线程 ID 打点定位
std::mutex 能跑通,不代表它没成为瓶颈;而过度拆细又容易引入新的竞态或死锁。这些边界,往往只在压测和真实负载下才暴露。











