必须用 RaiI(如 std::lock_guard 或 std::unique_lock)管理 std::mutex,禁用手动 lock/unlock;多锁需用 std::lock 或 std::scoped_lock 避免死锁;mutex 不可复制/移动;锁粒度宜细,临界区忌 I/O 与耗时操作。

std::mutex 必须配合 std::lock_guard 或 std::unique_lock 使用
直接调用 mutex.lock() 和 mutex.unlock() 极易出错:忘记 unlock、异常跳过 unlock、重复 unlock 都会导致未定义行为。c++ 标准库不鼓励手动管理锁生命周期。
正确做法是依赖 RAII —— 用 std::lock_guard(作用域自动加锁/解锁)或 std::unique_lock(支持延迟锁定、转移所有权、条件变量配合)。
-
std::lock_guard更轻量,构造即加锁,析构即解锁,不可复制,适合简单临界区 -
std::unique_lock功能更全,但有轻微开销;若只用默认构造,它不持有锁,需显式调用lock() - 绝不能把
std::lock_guard声明在 if 分支或循环内却期望保护外层逻辑 —— 作用域决定生命周期
多个 mutex 同时加锁必须用 std::lock 避免死锁
当一段逻辑需要同时持有两个或以上 std::mutex 时,如果分别调用 m1.lock() 和 m2.lock(),线程 A 先锁 m1 后等 m2,线程 B 先锁 m2 后等 m1,就会死锁。
标准解法是用 std::lock(m1, m2) —— 它使用“避免死锁算法”(如按地址排序尝试加锁),再配合 std::adopt_lock 构造 std::unique_lock:
立即学习“C++免费学习笔记(深入)”;
std::mutex m1, m2; std::lock(m1, m2); // 原子性获取两个锁 std::unique_lock guard1(m1, std::adopt_lock); std::unique_lock guard2(m2, std::adopt_lock);
- 不能对已 lock 的 mutex 再传给
std::lock,否则行为未定义 -
std::scoped_lock(C++17 起)是更简洁的替代:直接std::scoped_lock<:mutex std::mutex> lock(m1, m2);,内部已调用std::lock
std::mutex 不可复制、不可移动,成员变量声明要小心
std::mutex 是 move-only 类型(C++11 起禁用拷贝,也不提供移动构造/赋值),因此不能出现在需要拷贝的上下文中:
- 类中声明
std::mutex mtx;没问题,但该类自动删除拷贝构造函数和拷贝赋值运算符 - 若类需可拷贝,不能把
std::mutex作为直接成员;可改用std::shared_ptr<:mutex>(注意共享本身不解决线程安全,只是绕过拷贝限制) - vector<:mutex> 编译失败;要用
std::vector<:unique_ptr>>或预分配后 emplace_back - Lambda 捕获
[mtx = std::move(mtx)]无效 ——std::mutex不可移动,捕获只能用引用或指针
性能陷阱:锁粒度太粗或临界区含阻塞操作
常见低效模式是把整个函数体包进一个 std::lock_guard,尤其当临界区内含 I/O、sleep、网络调用或复杂计算时,会严重拖慢并发吞吐。
应只保护真正共享数据读写的部分,其余可并发执行:
void process_data() { int local_val = expensive_computation(); // ✅ 可并发执行 { std::lock_guard lock(mtx); shared_counter += local_val; // ✅ 仅此处需互斥 update_log(shared_counter); // ⚠️ 若 update_log 是 I/O,应移出临界区 } log_to_file("processed"); // ✅ 移出后,多线程可并行写日志 }
- 临界区内避免
std::cout、printf、文件写入、锁其他资源(如另一个 mutex) - 若必须在临界区做耗时操作,考虑用无锁结构(如
std::atomic)、读写锁(std::shared_mutex)或消息队列解耦 - 调试时加锁日志(如 “enter critical section”)本身会放大竞争,上线前务必移除
锁本身不解决所有线程安全问题;它只是工具。真正容易被忽略的是:共享状态的设计是否必要、数据是否真的需要跨线程修改、能否用线程局部存储(thread_local)或消息传递替代共享。(mutex 只管“谁在用”,不管“用得对不对”)