在C++中,std::lock_guard
、std::unique_lock
和std::shared_lock
是用于管理互斥量的RAII类,确保锁的正确获取和释放(避免忘记释放锁导致的死锁问题)。以下是它们的详细介绍、区别及使用场景:
1. std::lock_guard
- 功能:最简单的锁管理器,构造时立即锁定互斥量,析构时自动释放。
- 特点:
- 不支持手动锁定或解锁。
- 不支持延迟锁定或条件变量。
- 不可复制或移动,仅限当前作用域使用。
- 适用场景:
- 保护临界区,无需中途解锁或额外灵活性。
- 示例:
std::mutex mtx; { std::lock_guard<std::mutex> lock(mtx); // 立即锁定 // 临界区操作 } // 自动解锁
2. std::unique_lock
- 功能:灵活的锁管理器,支持手动控制锁,适用于复杂场景。
- 特点:
- 可延迟锁定(
defer_lock
)、尝试锁定(try_to_lock
)或接管已锁定互斥量(adopt_lock
)。 - 允许手动调用
lock()
和unlock()
。 - 支持所有权转移(移动语义)。
- 必须与条件变量配合使用。
- 可延迟锁定(
- 适用场景:
- 需要延迟锁定、中途解锁或条件变量。
- 锁的所有权需在不同作用域传递。
- 示例:
std::mutex mtx; std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟锁定 lock.lock(); // 手动锁定 // ... 临界区操作 lock.unlock(); // 手动解锁(可选)
std::unique_lock 提供了以下几个成员函数:
-
lock()
:尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁。 -
try_lock()
:尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false,否则返回 true。 -
try_lock_for(const std::chrono::duration<Rep, Period>& rel_time)
:尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间。
必须要用时间互斥锁。std::timed_mutex mtx;
try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time)
:尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点。
unlock()
:对互斥量进行解锁操作。
除了上述成员函数外,std::unique_lock 还提供了以下几个构造函数:
unique_lock() noexcept = default
:默认构造函数,创建一个未关联任何互斥量的 std::unique_lock 对象。
explicit unique_lock(mutex_type& m)
:构造函数,使用给定的互斥量 m 进行初始化,并对该互斥量进行加锁操作。
unique_lock(mutex_type& m, defer_lock_t) noexcept
:构造函数,使用给定的互斥量 m 进行初始化,但不对该互斥量进行加锁操作。
unique_lock(mutex_type& m, try_to_lock_t) noexcept
:构造函数,使用给定的互斥量 m 进行初始化,并尝试对该互斥量进行加锁操作。如果加锁失败,则创建的 std::unique_lock 对象不与任何互斥量关联。
unique_lock(mutex_type& m, adopt_lock_t) noexcept
:构造函数,使用给定的互斥量 m 进行初始化,并假设该互斥量已经被当前线程成功加锁。
3. std::shared_lock
- 功能:管理共享互斥量(如
std::shared_mutex
)的共享锁,允许多线程并发读。 - 特点:
- 构造时以共享模式锁定(
lock_shared()
)。 - 支持延迟锁定、手动操作,类似
std::unique_lock
。 - 析构时自动释放共享锁。
- 构造时以共享模式锁定(
- 适用场景:
- 多线程读取共享数据,需并发访问。
- 写入时需配合
std::unique_lock
或std::lock_guard
进行独占锁定。
- 示例:
std::shared_mutex sh_mtx; { std::shared_lock<std::shared_mutex> read_lock(sh_mtx); // 共享锁定 // 多线程可并发读取 } // 自动释放共享锁 { std::lock_guard<std::shared_mutex> write_lock(sh_mtx); // 独占锁定 // 单线程写入 }
关键区别总结
特性 | std::lock_guard | std::unique_lock | std::shared_lock |
---|---|---|---|
锁定模式 | 独占 | 独占 | 共享 |
灵活性 | 低(自动锁定/解锁) | 高(手动控制) | 中(类似unique_lock ) |
延迟锁定 | 不支持 | 支持(defer_lock ) | 支持(defer_lock ) |
条件变量支持 | 不支持 | 支持 | 不支持 |
适用互斥量类型 | 普通互斥量(如std::mutex ) | 普通互斥量 | 共享互斥量(如std::shared_mutex ) |
性能开销 | 低 | 较高 | 中等 |
选择建议
- 简单临界区:优先用
std::lock_guard
,轻量且高效。 - 复杂控制(如条件变量):用
std::unique_lock
。 - 多读单写场景:读用
std::shared_lock
,写用std::unique_lock
或std::lock_guard
。
通过合理选择锁管理器,可以避免死锁并提升多线程程序的性能和可靠性。
我悠哉悠哉邀请我的灵魂,弯腰闲看一片夏天的草叶。 —沃尔特·惠特曼