std::lock_guard、std::unique_lock、std::shared_lock

在C++中,std::lock_guardstd::unique_lockstd::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_lockstd::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_guardstd::unique_lockstd::shared_lock
锁定模式独占独占共享
灵活性低(自动锁定/解锁)高(手动控制)中(类似unique_lock
延迟锁定不支持支持(defer_lock支持(defer_lock
条件变量支持不支持支持不支持
适用互斥量类型普通互斥量(如std::mutex普通互斥量共享互斥量(如std::shared_mutex
性能开销较高中等

选择建议

  • 简单临界区:优先用std::lock_guard,轻量且高效。
  • 复杂控制(如条件变量):用std::unique_lock
  • 多读单写场景:读用std::shared_lock,写用std::unique_lockstd::lock_guard

通过合理选择锁管理器,可以避免死锁并提升多线程程序的性能和可靠性。


我悠哉悠哉邀请我的灵魂,弯腰闲看一片夏天的草叶。 —沃尔特·惠特曼

### std::lock_guard<std::mutex> 导致程序崩溃的原因 当 `std::lock_guard` 使用过程中抛出异常,通常是因为底层互斥量操作失败。具体来说,在 Visual Studio (VS) 中可能会遇到如下情况: - 如果尝试锁定已经处于锁定状态的互斥体,则会触发异常 `std::system_error` [^1]。 这种情况下,通常是由于编程逻辑错误引起的竞态条件或重复进入临界区造成的死锁状况所引发的问题。 另外需要注意的是,现代C++标准库推荐使用 `std::scoped_lock` 替代传统的 `std::lock_guard` 来管理多个互斥量的同时加锁需求 [^2]。 ### 解决方案 为了防止因 `std::lock_guard` 而产生的崩溃问题,可以采取以下措施之一: #### 方法一:改用 `std::unique_lock` 相比于 `std::lock_guard` ,`std::unique_lock` 提供更灵活的操作方式,允许手动控制何时上锁和解锁,并支持延迟绑定到互斥量实例。这有助于避免某些特定场景下的潜在风险 [^3]。 ```cpp #include <iostream> #include <thread> #include <mutex> void thread_function(std::unique_ptr<int>& data, std::mutex& mtx) { std::unique_lock<std::mutex> lck(mtx); // 手动处理资源访问... } ``` #### 方法二:采用 `std::scoped_lock` 对于需要一次性获取多个互斥量的情况,建议直接切换至 `std::scoped_lock` 。该类不仅简化了语法结构,更重要的是能够有效预防死锁的发生 。 ```cpp #include <iostream> #include <thread> #include <mutex> void shared_resource_access() { std::mutex m1, m2; std::scoped_lock guard{m1, m2}; // 同时获得两个 mutex // 安全地执行多线程共享数据操作... } ``` 通过上述两种方法中的任意一种都可以有效地减少甚至消除由 `std::lock_guard` 引发的应用程序不稳定现象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值