std::unique_lock与std::lock_guard

一、介绍

  lock_guard和unique_lock都是RAII机制下的锁,即依靠对象的创建和销毁也就是其生命周期来自动实现一些逻辑,而这两个对象就是在创建时自动加锁,在销毁时自动解锁。所以如果仅仅是依靠对象生命周期实现加解锁的话,两者是相同的,都可以用,因跟生命周期有关,所以有时会用花括号指定其生命周期。但lock_guard的功能仅限于此unique_lock是对lock_guard的扩展,允许在生命周期内再调用lock和unlock来加解锁以切换锁的状态

二、区别

  c++11中有两个区域锁:lock_guard和unique_lock。

  • 区域锁lock_guard使用起来比较简单,除了构造函数外没有其他member function,在整个区域都有效。
  • 区域锁unique_lock除了lock_guard的功能外,提供了更多的member_function,相对来说更灵活一些;但是在unique_lock占用地内存更多,效率也有所下降。

三、unique_lock和lock_guard

3.1 声明

unique_lock相交于lock_guard更加灵活,可以手动进行解锁,但是在日常编程中,还是以lock_guard为主。但是标准库也提供了第二参数的构造函数。例如:

1 explicit lock_guard (mutex_type& m);
2 lock_guard (mutex_type& m, adopt_lock_t tag);
3 
4 explicit unique_lock (mutex_type& m);
5 unique_lock (mutex_type& m, try_to_lock_t tag);
6 unique_lock (mutex_type& m, defer_lock_t tag) noexcept;
7 unique_lock (mutex_type& m, adopt_lock_t tag);
8 ... ...

3.1 unique_lock部分成员函数

 unique_lock的最有用的一组函数为:

locks the associated mutex 
(public member function)
tries to lock the associated mutex, returns if the mutex is not available 
(public member function)
attempts to lock the associated TimedLockable mutex, returns if the mutex has been unavailable for the specified time duration 
(public member function)
tries to lock the associated TimedLockable mutex, returns if the mutex has been unavailable until specified time point has been reached 
(public member function)
unlocks the associated mutex 
  • 通过上面的函数,可以通过lock/unlock可以比较灵活的控制锁的范围,减小锁的粒度。
  • 通过try_lock_for/try_lock_until则可以控制加锁的等待时间,此时这种锁为乐观锁。

3.2 unique_lock取代lock_guard

unique_lock是个类模板,工作中,一般lock_guard(推荐使用);
lock_guard取代了mutex的lock()和unlock()。
unique_lock比lock_guard灵活很多灵活很多;效率上差一点,内存占用多一点

使用时std::lock_guard<std::mutex> lk(mtx);直接替换成std::unique_lock<std::mutex> lk(mtx);

3.3 lock_guard和unique_lock第二参数的作用

3.3.1 std::adopt_lock

  std::adopt_lock标记的效果就是假设调用一方已经拥有了互斥量的所有权(已经lock成功了);通知lock_guard不需要再构造函数中lock这个互斥量了。

1 std::lock_guard<std::mutex> sbguard(my_mutex,std::adopt_lock);

  对于lock_guard第二参数类型只有一种,锁管理器构造的时候不会自动对可锁对象上锁;由可锁对象自己加锁;等锁管理器析构的时候自动解锁

两种使用方法

1  {
2     std::lock_guard<std::mutex> lock(g_mtx, std::adopt_lock);
3     g_mtx.lock();
4     临界区或临界资源
5 } 

或者

1 {
2     g_mtx.lock();
3     std::lock_guard<std::mutex> lock(g_mtx, std::adopt_lock);
4     临界区或临界资源
5 } 

如果我们指定了第二参数,但是没有lock,锁管理器析构的时候解锁了无拥有权的可锁对象,导致异常。

  为什么需要使用第二参数构造函数;直接在锁管理器构造的时候自动加锁不好吗?其实官方提供第二参数构造函数是有其他作用的。比如多锁场景下,我们会调用std::lock避免死锁的出现,但是这个方法要求锁管理器不能拥有可锁对象,由std::lock方法执行锁操作。如果没有提供第二参数构造函数,那么我们就无法使用该方法了。

  std::adopt_lock和lock_guard第二参数作用类似。锁管理器假设拥有可锁对象,在锁管理器析构的时候自动解锁。注意:使用该参数类型构造的锁管理器必须只能通过可锁对象进行lock,不可通过锁管理器进行lock,误用会导致程序异常

3.3.2 std::defer_lock

  用std::defer_lock的前提是,你不能自己先lock,否则会报异常,std::defer_lock的意思就是并没有给mutex加锁:初始化了一个没有加锁的mutex。

 

 

1 {
2    std::unique_lock<std::mutex> lock(g_mtx, std::defer_lock);
3     lock.lock();           // 不能用g_mtx.lock(),第二次锁的时候会崩溃
4     临界区或临界资源
5 }

  std::defer_lock参数作用:锁管理器在构造的时候不主动lock且不拥有可锁对象;如果后续执行lock,锁管理器析构的时候自动解锁。注意:该类型构造的锁管理器只能通过锁管理器执行lock且拥有可锁对象。如果直接调用可锁对象进行锁操作后,会导致程序异常;详情可参考源码。

3.3.2 std::try_to_lock

1 {
2      std::unique_lock<std::mutex> lock(g_mtx, std::try_to_lock);
3      if (lock.owns_lock()) {
4          临界区或临界资源
5      }
6 }

  std::try_to_lock参数作用:锁管理器在构造的时候尝试lock;如果lock上,锁管理器就拥有可锁对象(持有锁),析构的时候自动执行解锁;否则就不持可锁对象,析构的时候也就不会解锁;它的好处是当某个线程尝试获取该该锁,但是该锁已经已被其他线程持有,那么不会出现线程被阻塞挂起

四、参考文章

https://www.cnblogs.com/smartNeo/p/14624156.html

https://blog.csdn.net/u012507022/article/details/85909567?utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EsearchFromBaidu%7Edefault-1.pc_relevant_baidujshouduan&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EsearchFromBaidu%7Edefault-1.pc_relevant_baidujshouduan

posted @ 2021-07-30 20:06  Mr-xxx  阅读(2596)  评论(0编辑  收藏  举报