c++如何使用互斥锁mutex_c++多线程同步教程【示例】

9次阅读

必须用RaiI管理std::mutex:优先std::lock_guard(简单场景)或std::unique_lock(需try_lock/condition_variable等),禁用手动lock/unlock;多锁须按固定顺序或用std::scoped_lock避免死锁;shared_mutex慎用于写频繁场景,且不可复制移动。

c++如何使用互斥锁mutex_c++多线程同步教程【示例】

std::mutex 必须配合 std::lock_guard 或 std::unique_lock 使用

直接调用 mutex.lock()mutex.unlock() 极易出错——比如异常抛出后忘记解锁,导致死锁。c++ 标准库不鼓励手动管理锁的生命周期。

正确做法是依赖 RAII:用 std::lock_guard作用域自动加锁/解锁)或 std::unique_lock(支持延迟锁定、转移所有权等高级操作)。

  • std::lock_guard 更轻量,适用于“进作用域就锁、出作用域就放”的简单场景
  • std::unique_lock 开销略大,但支持 try_lock()unlock() 手动释放、与 std::condition_variable 配合
  • 绝不要在同一个线程中对已持有的 std::mutex 再次调用 lock() ——这是未定义行为,不是可重入锁

多个 mutex 按固定顺序加锁,避免死锁

当一段逻辑需要同时持有两个或以上互斥锁时,如果线程 A 先锁 mtx1 再锁 mtx2,而线程 B 反过来先锁 mtx2 再锁 mtx1,就可能形成环形等待。

解决方法是全局约定加锁顺序(例如按地址大小、按变量声明顺序),或使用 std::scoped_lock(C++17 起)一次性按无序方式安全加锁:

立即学习C++免费学习笔记(深入)”;

std::mutex mtx1, mtx2; // 安全:自动按内部顺序加锁,不会死锁 std::scoped_lock lock(mtx1, mtx2);
  • std::scoped_lockstd::lock_guard 的多锁升级版,构造即加锁,析构即解锁
  • 旧代码若用 std::lock(mtx1, mtx2) + std::lock_guard 手动构造,容易漏掉 adopt_lock 参数,导致重复加锁崩溃
  • 别用 std::try_lock 循环抢锁代替顺序约定——它只是绕开问题,不能根除竞争逻辑缺陷

shared_mutex 适合读多写少场景,但注意 writer starvation

std::shared_mutex(C++17)允许多个 reader 并发访问,writer 独占访问。但它不保证公平性:持续的 reader 请求可能让 writer 无限等待。

如果你的场景写操作频率不低,或对写延迟敏感,std::shared_mutex 反而比普通 std::mutex 更慢、更难调试。

  • reader 使用 std::shared_lock<:shared_mutex>
  • writer 使用 std::unique_lock<:shared_mutex>
  • windows 上对应的是 SRWLOCKlinux glibc 实现早期有性能问题,建议用较新编译器(GCC 9+/Clang 8+)
  • 不要在持有 shared_lock 期间调用可能阻塞或抛异常的函数——否则 reader 长时间不释放,会拖垮 writer

std::mutex 不能跨线程复制或移动,只支持默认构造和销毁

std::mutex 是不可复制、不可移动的类型。试图把它放进 std::vector<:mutex> 或作为结构体成员被拷贝,编译直接报错:

error: use of deleted function 'std::mutex::mutex(const std::mutex&)'

常见误用包括:

  • std::mutex 当作普通成员变量,在类赋值运算符中没显式忽略它
  • 想用 std::make_shared() 创建含 mutex 的对象,却忘了 MyClass构造函数必须显式初始化 mutex(默认构造即可)
  • 在线程函数里传入局部 std::mutex 的引用——该 mutex 随函数返回被销毁,其他线程再访问就是 dangling reference

真正需要“多个锁”时,应静态分配、全局唯一,或用指针/智能指针管理生命周期,而不是尝试复制锁本身。

text=ZqhQzanResources