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 只被调用一次
}
五、死锁与性能注意
- 锁的顺序
- 对多个互斥量加锁时,保持全局一致的顺序或使用
std::scoped_lock
。
- 对多个互斥量加锁时,保持全局一致的顺序或使用
- 减少锁粒度
- 临界区只保护必要的共享数据;避免大范围持锁导致性能瓶颈。
- 避免递归锁滥用
recursive_mutex
灵活但开销大,可读设计是否真需要同一线程重入。
- 公平性与饥饿
- 大多数实现非公平排队,高优先级或频繁加锁线程可能“饥饿”低优先级线程。可根据需要选用读写锁或更高层框架。
- 条件变量配合
std::condition_variable
(在<condition_variable>
中)需要std::unique_lock
,不是lock_guard
。
六、实践建议
- 优先使用高层并行框架
现代 C++ 更推荐使用<atomic>
、线程池、并行算法(<execution>
)或任务系统,直接管理 mutex/lock 应仅在必要时进行细粒度控制。 - 封装共享数据结构
将互斥与数据一起封装到类内,通过成员函数对外统一接口,保证使用者无法绕过锁。 - 避免繁重计算持锁
如果需要大量 CPU 工作,可在释放锁后再进行,以降低锁竞争。 - 工具监测
在调试阶段可使用 ThreadSanitizer(TSan)等工具检测死锁和数据竞争。
通过以上对 <mutex>
中互斥量类型、锁管理类、一次性调用及实践注意事项的全面梳理,希望能帮助你在多线程编程中更安全、高效地使用标准库同步原语。祝编码顺利!