C++ 标准库 mutex

下面对 C++11 起引入的 <mutex> 头文件及其实用同步原语做一次系统、深入的梳理,包括互斥量类型、锁管理类、一次性调用、实践建议等。


一、概述

  • <mutex> 提供了基于操作系统底层原生机制的互斥量(mutex)及相关锁管理工具,用于在线程间保护共享资源,避免数据竞争。
  • 所有类型都在命名空间 std 下,满足 RAII 风格,确保异常安全。

二、互斥量类型

类型特性
std::mutex最基础、不可重入的互斥量。支持 lock()unlock()try_lock()
std::recursive_mutex允许同一线程多次锁定,匹配的次数必须相同次数解锁。
std::timed_mutex在 lock_for()lock_until() 上支持限时尝试加锁。
std::recursive_timed_mutex递归 + 限时尝试加锁。
std::shared_mutex (C++17)读–写锁:支持多个共享(读)锁或独占(写)锁。
std::shared_timed_mutex (C++14)同上,且支持限时尝试加锁。

示例:

std::mutex m;
if (m.try_lock()) {
    // 成功获得互斥
    m.unlock();
}

三、锁管理类

1. std::lock_guard<Mut>

  • 最简单的 RAII 锁:构造时 lock(),析构时 unlock()
  • 不可手动解锁,不可转移所有权。
std::mutex m;
void f() {
    std::lock_guard<std::mutex> lk(m);
    // m 已锁,函数退出时自动 unlock
}

2. std::unique_lock<Mut>

  • 功能更强:可延迟锁定、手动 lock()/unlock()、可中途释放/重新加锁,还能与条件变量配合。
  • 支持可转移所有权(move-only)。
std::mutex m;
void g() {
    std::unique_lock<std::mutex> lk(m, std::defer_lock);
    // …决定时机
    lk.lock();
    // …临界区
    lk.unlock();
    // …再锁
}

3. std::scoped_lock<Mut...> (C++17)

  • 同时对多个互斥量加锁,避免死锁:构造时锁定所有,析构时统一解锁。
std::mutex m1, m2;
void h() {
    std::scoped_lock lk(m1, m2);
    // 同时锁住 m1 和 m2
}

四、一次性调用:std::call_once 与 std::once_flag

  • 场景:需要某段初始化代码仅执行一次(如单例初始化)。
  • std::once_flag flag;
  • std::call_once(flag, []{/* 初始化 */}); ——无论多少线程并发调用,内部保证目标函数只执行一次,其余线程阻塞或快速返回。
std::once_flag init_flag;
void init() { /* … */ }
void use() {
    std::call_once(init_flag, init);
    // 保证 init 只被调用一次
}

五、死锁与性能注意

  1. 锁的顺序
    • 对多个互斥量加锁时,保持全局一致的顺序或使用 std::scoped_lock
  2. 减少锁粒度
    • 临界区只保护必要的共享数据;避免大范围持锁导致性能瓶颈。
  3. 避免递归锁滥用
    • recursive_mutex 灵活但开销大,可读设计是否真需要同一线程重入。
  4. 公平性与饥饿
    • 大多数实现非公平排队,高优先级或频繁加锁线程可能“饥饿”低优先级线程。可根据需要选用读写锁或更高层框架。
  5. 条件变量配合
    • std::condition_variable(在 <condition_variable> 中)需要 std::unique_lock,不是 lock_guard

六、实践建议

  • 优先使用高层并行框架
    现代 C++ 更推荐使用 <atomic>、线程池、并行算法(<execution>)或任务系统,直接管理 mutex/lock 应仅在必要时进行细粒度控制。
  • 封装共享数据结构
    将互斥与数据一起封装到类内,通过成员函数对外统一接口,保证使用者无法绕过锁。
  • 避免繁重计算持锁
    如果需要大量 CPU 工作,可在释放锁后再进行,以降低锁竞争。
  • 工具监测
    在调试阶段可使用 ThreadSanitizer(TSan)等工具检测死锁和数据竞争。

通过以上对 <mutex> 中互斥量类型、锁管理类、一次性调用及实践注意事项的全面梳理,希望能帮助你在多线程编程中更安全、高效地使用标准库同步原语。祝编码顺利!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注