C++11 std::unique_lock与std::lock_guard区别及多线程应用实例

本文介绍C++11中std::lock_guard与std::unique_lock两种锁机制的使用方法及区别,前者用于简单的自动加解锁场景,后者提供更灵活的锁管理,适用于需要在等待期间释放锁的情况。

C++多线程编程中通常会对共享的数据进行写保护,以防止多线程在对共享数据成员进行读写时造成资源争抢导致程序出现未定义的行为。通常的做法是在修改共享数据成员的时候进行加锁--mutex。在使用锁的时候通常是在对共享数据进行修改之前进行lock操作,在写完之后再进行unlock操作,进场会出现由于疏忽导致由于lock之后在离开共享成员操作区域时忘记unlock,导致死锁。

针对以上的问题,C++11中引入了std::unique_lock与std::lock_guard两种数据结构。通过对lock和unlock进行一次薄的封装,实现自动unlock的功能。

  1. std::mutex mut;  
  2.   
  3. void insert_data()  
  4. {  
  5.        std::lock_guard<std::mutex> lk(mut);  
  6.        queue.push_back(data);  
  7. }  
  8.   
  9. void process_data()  
  10. {  
  11.        std::unqiue_lock<std::mutex> lk(mut);  
  12.        queue.pop();  
  13. }  
std::unique_lock 与std::lock_guard都能实现自动加锁与解锁功能,但是std::unique_lock要比std::lock_guard更灵活,但是更灵活的代价是占用空间相对更大一点且相对更慢一点。

通过实现一个线程安全的队列来说明两者之间的差别。

  1. template <typename T>  
  2. class ThreadSafeQueue{  
  3. public:  
  4.          void Insert(T value);  
  5.          void Popup(T &value);  
  6.          bool Empety();  
  7.   
  8. private:  
  9.        mutable std::mutex mut_;  
  10.        std::queue<T> que_;  
  11.        std::condition_variable cond_;  
  12. };  
  1. template <typename T>  
  2. void ThreadSafeQueue::Insert(T value){  
  3.     std::lock_guard<std::mutex> lk(mut_);  
  4.     que_.push_back(value);  
  5.     cond_.notify_one();  
  6. }  
  7.   
  8.   
  9. template <typename T>  
  10. void ThreadSafeQueue::Popup(T &value){  
  11.     std::unique_lock<std::mutex> lk(mut_);  
  12.     cond_.wait(lk, [this]{return !que_.empety();});  
  13.     value = que_.front();  
  14.     que_.pop();  
  15. }  
  16.   
  17.   
  18. template <typename T>  
  19. bool ThreadSafeQueue::Empty() const{  
  20.         std::lock_guard<std::mutex> lk(mut_);  
  21.         return que_.empty();  
  22. }  
上面代码只实现了关键的几个函数,并使用了C++11新引入的condition_variable条件变量。从Popup与Inert两个函数看std::unique_lock相对std::lock_guard更灵活的地方在于在等待中的线程如果在等待期间需要解锁mutex,并在之后重新将其锁定。而std::lock_guard却不具备这样的功能。

上面代码中

  1. cond_.wait(lk, [this]{return !Empety();});  
可能会比较难以理解,
  1. [this]{return !Empety();}  
是C++11新引入的功能,lambda表达式,是一种匿名函数。方括号内表示捕获变量。当lambda表达式返回true时(即queue不为空),wait函数会锁定mutex。当lambda表达式返回false时,wait函数会解锁mutex同时会将当前线程置于阻塞或等待状态。

还存在另一种读写锁,但是并没有引入C++11,但是boost库提供了对应的实现。读写锁主要适合在于共享数据更新频率较低,但是读取共享数据频率较高的场合。

C++11多线程编程中,`std::unique_lock` `std::timed_mutex` 的结合使用提供了一种灵活的锁机制,尤其适用于需要延迟加锁(deferred locking)或超时控制的场景。通过 `std::defer_lock`,可以创建一个不立即锁定互斥量的 `std::unique_lock` 实例,从而允许后续在合适的时机手动加锁。 ### 延迟加锁的使用方式 当使用 `std::unique_lock<std::timed_mutex>` `std::defer_lock` 时,构造 `std::unique_lock` 对象时不会锁定关联的 `std::timed_mutex`。这允许开发者在构造之后调用 `lock()`、`try_lock()` 或 `try_lock_for()` 等方法来控制加锁时机。 例如: ```cpp std::timed_mutex tm; std::unique_lock<std::timed_mutex> lock(tm, std::defer_lock); // 延迟加锁,可在后续任意时间调用 lock() lock.lock(); ``` 上述代码中,`std::defer_lock` 参数表明在构造 `std::unique_lock` 时不立即加锁,而是延迟到调用 `lock()` 方法时才进行加锁操作[^3]。 ### 超时机制结合使用 `std::timed_mutex` 支持带有超时的加锁操作,如 `try_lock_for()` 和 `try_lock_until()`,这使得 `std::unique_lock` 可以实现更复杂的同步策略。例如,在等待资源可用时设置最大等待时间: ```cpp std::timed_mutex tm; std::unique_lock<std::timed_mutex> lock(tm, std::defer_lock); if (lock.try_lock_for(std::chrono::seconds(5))) { // 成功加锁,执行临界区代码 } else { // 超时未加锁,处理超时逻辑 } ``` 在这个例子中,`try_lock_for()` 尝试在 5 秒内获取锁,若成功则执行临界区代码,否则返回失败。 ### 优势适用场景 - **延迟加锁**:适用于需要在多个线程中动态决定是否加锁或在特定条件下加锁的场景。 - **超时控制**:适用于需要避免死锁或长时间阻塞的情况,如资源竞争激烈的环境中。 - **灵活的解锁控制**:`std::unique_lock` 允许在作用域内手动控制加锁和解锁的时间点,适合复杂的同步逻辑。 综上所述,`std::unique_lock` `std::timed_mutex` 和 `std::defer_lock` 的结合使用,提供了比 `std::lock_guard` 更为灵活的锁管理机制,适用于需要精细控制加锁时机和超时行为的多线程编程场景[^1]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值