搜狐媒体平台-搜狐网站>IT

他一出家就成中国最帅和尚

眼眸深邃、轮廓分明、身材颀长,活生生的一幅画。

大学副教授与在押服刑女结婚

这在监狱民警看来,那么令人不可思议。

C++ 11 中的 mutex, lock, condition variable 实现分析

cpp开发者mp 阅读(0) 评论()
声明:本文由入驻搜狐公众平台的作者撰写,除搜狐官方账号外,观点仅代表作者本人,不代表搜狐立场。举报

(点击上方公众号,可快速关注)

  来源:hengyunabc

  链接:https://blog.csdn.net/hengyunabc/article/details/33336795

  本文分析的是llvm libc++的实现:https://libcxx.llvm.org/

  C++11中的各种mutex, lock对象,实际上都是对posix的mutex,condition的封装。不过里面也有很多细节值得学习。

  std::mutex

  先来看下std::mutex:

  包增了一个pthread_mutex_t __m_,很简单,每个函数该干嘛就干嘛。

  三种锁状态:std::defer_lock, std::try_to_lock, std::adopt_lock

  这三个是用于标识锁在传递到一些包装类时,锁的状态:

  • std::defer_lock,还没有获取到锁

  • std::try_to_lock,在包装类构造时,尝试去获取锁

  • std::adopt_lock,调用者已经获得了锁

  这三个东东,实际上是用于偏特化的,是三个空的struct:

  

  在下面的代码里,就可以看到这三个东东是怎么用的了。

  std::lock_guard

  这个类比较重要,因为我们真正使用lock的时候,大部分都是要用这个。

  这个类其实很简单:

  在构造函数里调用 mutext.lock(),

  在释构函数里,调用了mutex.unlock() 函数。

  因为C++会在函数抛出异常时,自动调用作用域内的变量的析构函数,所以使用std::lock_guard可以在异常时自动释放锁,这就是为什么要避免直接使用mutex的函数,而是要用std::lock_guard的原因了。

  

  注意,std::lock_guard的两个构造函数,当只传递mutex时,会在构造函数时调用mutext.lock()来获得锁。

  当传递了adopt_lock_t时,说明调用者已经拿到了锁,所以不再尝试去获得锁。

  std::unique_lock

  unique_lock实际上也是一个包装类,起名为unique可能是和std::lock函数区分用的。

  注意,多了一个owns_lock函数和release()函数,这两个在std::lock函数会用到。

  owns_lock函数用于判断是否拥有锁;

  release()函数则放弃了对锁的关联,当析构时,不会去unlock锁。

  再看下unique_lock的实现,可以发现,上面的三种类型就是用来做偏特化用的:

  

  

  std::lock 和 std::try_lock 函数

  上面的都是类对象,这两个是函数。

  std::lock和std::try_lock函数用于在同时使用多个锁时,防止死锁。这个实际上很重要的,因为手写代码来处理多个锁的同步问题,很容易出错。

  要注意的是std::try_lock函数的返回值:

  当成功时,返回-1;

  当失败时,返回第几个锁没有获取成功,以0开始计数;

  首先来看下只有两个锁的情况,代码虽然看起来比较简单,但里面却有大文章:

  template<class_L0, class_L1>

  void lock(_L0& __l0,_L1& __l1)

  {

  while(true)

  {

  {

  unique_lock __u0(__l0);

  if(__l1.try_lock())//已获得锁l0,再尝试获取l1

  {

  __u0.release();//l0和l1都已获取到,因为unique_lock在释构时会释放l0,所以要调用release()函数,不让它释放l0锁。

  break;

  }

  }//如果同时获取l0,l1失败,这里会释放l0。

  sched_yield();//把线程放到同一优先级的调度队列的尾部,CPU切换到其它线程执行

  {

  unique_lock __u1(__l1);//因为上面尝试先获取l1失败,说明有别的线程在持有l1,那么这次先尝试获取锁l1(只有前面的线程释放了,才可能获取到)

  if(__l0.try_lock())

  {

  __u1.release();

  break;

  }

  }

  sched_yield();

  }

  }

  template<class_L0, class_L1>

  int try_lock(_L0& __l0,_L1& __l1)

  {

  unique_lock __u0(__l0,try_to_lock);

  if(__u0.owns_lock())

  {

  if(__l1.try_lock())//注意try_lock返回值的定义,否则这里无法理解

  {

  __u0.release();

  return-1;

  }

  else

  return1;

  }

  return0;

  }

  上面的lock函数用尝试的办法防止了死锁。

  上面是两个锁的情况,那么在多个参数的情况下呢?

  先来看下std::try_lock函数的实现:

  里面递归地调用了try_lock函数自身,如果全部锁都获取成功,则依次把所有的unique_lock都release掉。

  如果有失败,则计数失败的次数,最终返回。

  template<class_L0, class_L1, class_L2, class... _L3>

  int try_lock(_L0& __l0,_L1& __l1,_L2& __l2,_L3&...__l3)

  {

  int__r= 0;

  unique_lock __u0(__l0,try_to_lock);

  if(__u0.owns_lock())

  {

  __r= try_lock(__l1,__l2,__l3...);

  if(__r== -1)

  __u0.release();

  else

  ++__r;

  }

  return__r;

  }

  再来看多参数的std::lock的实现:

  template<class_L0, class_L1, class_L2, class..._L3>

  void __lock_first(int__i,_L0& __l0,_L1& __l1,_L2& __l2,_L3& ...__l3)

  {

  while(true)

  {

  switch(__i)//__i用来标记上一次获取参数里的第几个锁失败,从0开始计数

  {

  case0: //第一次执行时,__i是0

  {

  unique_lock __u0(__l0);

  __i= try_lock(__l1,__l2,__l3...);

  if(__i== -1)//获取到l0之后,如果尝试获取后面的锁也成功了,即全部锁都获取到了,则设置unique_lock为release,并返回

  {

  __u0.release();

  return;

  }

  }

  ++__i;//因为__i表示是获取第几个锁失败,而上面的try_lock(__l1,__l2__l3,...)是从l1开始的,因此这里要+1,调整到没有获取成功的锁上,下次先从它开始获取。

  sched_yield();

  break;

  case1: //说明上次获取l1失败,这次先获取到l1。

  {

  unique_lock __u1(__l1);

  __i= try_lock(__l2,__l3...,__l0);//把前一次的l0放到最后。这次先获取到了l1,再尝试获取后面的锁。

  if(__i== -1)

  {

  __u1.release();

  return;

  }

  }

  if(__i== sizeof...(_L3)+ 1)//说明把l0放到最后面时,最后获取l0时失败了。那么说明现在有其它线程持有l0,那么下一次要从l0开始获取。

  __i= 0;

  else

  __i+= 2;//因为__i表示是获取第几个锁失败,而上面的try_lock(__l2,__l3..., __l0)是从l2开始的,因此这里要+2

  sched_yield();

  break;

  default:

  __lock_first(__i- 2,__l2,__l3...,__l0,__l1);//因为这里是从l2开始的,因此__i要减2。

  return;

  }

  }

  }

  template<class_L0, class_L1, class_L2, class..._L3>

  inline_LIBCPP_INLINE_VISIBILITY

  void lock(_L0& __l0,_L1& __l1,_L2& __l2,_L3& ...__l3)

  {

  __lock_first(0,__l0,__l1,__l2,__l3...);

  }

  可以看到多参数的std::lock的实现是:

  先获取一个锁,然后再调用std::try_lock去获取剩下的锁,如果失败了,则下次先获取上次失败的锁。

  重复上面的过程,直到成功获取到所有的锁。

  上面的算法用比较巧妙的方式实现了参数的轮转。

  std::timed_mutex

  std::timed_mutex 是里面封装了mutex和condition,这样就两个函数可以用:

  try_lock_for

  try_lock_until

  实际上是posix的mutex和condition的包装。

  classtimed_mutex

  {

  mutex __m_;

  condition_variable __cv_;

  bool__locked_;

  public:

  timed_mutex();

  ~timed_mutex();

  private:

  timed_mutex(consttimed_mutex&);// = delete;

  timed_mutex& operator=(consttimed_mutex&);// = delete;

  public:

  voidlock();

  booltry_lock()_NOEXCEPT;

  template<class_Rep, class_Period>

  _LIBCPP_INLINE_VISIBILITY

  booltry_lock_for(constchrono::duration& __d)

  {returntry_lock_until(chrono::steady_clock::now()+ __d);}

  template<class_Clock, class_Duration>

  booltry_lock_until(constchrono::time_point& __t);

  voidunlock()_NOEXCEPT;

  };

  template<class_Clock, class_Duration>

  bool timed_mutex::try_lock_until(constchrono::time_point& __t)

  {

  usingnamespacechrono;

  unique_lock __lk(__m_);

  boolno_timeout= _Clock::now();

  

  std::recursive_mutex和std::recursive_timed_mutex

  这两个实际上是std::mutex和std::timed_mutex 的recursive模式的实现,即锁得获得者可以重复多次调用lock()函数。

  和posix mutex里的recursive mutex是一样的。

  看下std::recursive_mutex的构造函数就知道了。

  recursive_mutex::recursive_mutex()

  {

  pthread_mutexattr_t attr;

  intec= pthread_mutexattr_init(&attr);

  if(ec)

  gotofail;

  ec= pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);

  if(ec)

  {

  pthread_mutexattr_destroy(&attr);

  gotofail;

  }

  ec= pthread_mutex_init(&__m_,&attr);

  if(ec)

  {

  pthread_mutexattr_destroy(&attr);

  gotofail;

  }

  ec= pthread_mutexattr_destroy(&attr);

  if(ec)

  {

  pthread_mutex_destroy(&__m_);

  gotofail;

  }

  return;

  fail:

  __throw_system_error(ec,"recursive_mutex constructor failed");

  }

  std::cv_status

  这个用来表示condition等待返回的状态的,和上面的三个表示lock的状态的用途差不多。

  enumcv_status

  {

  no_timeout,

  timeout

  };

  std::condition_variable

  包装了posix condition variable。

  classcondition_variable

  {

  pthread_cond_t __cv_;

  public:

  condition_variable(){__cv_= (pthread_cond_t)PTHREAD_COND_INITIALIZER;}

  ~condition_variable();

  private:

  condition_variable(constcondition_variable&);// = delete;

  condition_variable& operator=(constcondition_variable&);// = delete;

  public:

  voidnotify_one()_NOEXCEPT;

  voidnotify_all()_NOEXCEPT;

  voidwait(unique_lock& __lk)_NOEXCEPT;

  template<class_Predicate>

  voidwait(unique_lock& __lk,_Predicate __pred);

  template<class_Clock, class_Duration>

  cv_status wait_until(unique_lock& __lk,

  constchrono::time_point& __t);

  template<class_Clock, class_Duration, class_Predicate>

  boolwait_until(unique_lock& __lk,

  constchrono::time_point& __t,

  _Predicate __pred);

  template<class_Rep, class_Period>

  cv_statuswait_for(unique_lock& __lk,

  constchrono::duration& __d);

  template<class_Rep, class_Period, class_Predicate>

  bool wait_for(unique_lock& __lk,

  constchrono::duration& __d,

  _Predicate __pred);

  typedefpthread_cond_t* native_handle_type;

  _LIBCPP_INLINE_VISIBILITY native_handle_type native_handle(){return&__cv_;}

  private:

  void__do_timed_wait(unique_lock& __lk,

  chrono::time_point)_NOEXCEPT;

  };

  里面的函数都是符合直觉的实现,值得注意的是:

  cv_status是通过判断时间而确定的,如果超时的则返回cv_status::timeout,如果没有超时,则返回cv_status::no_timeout。

  condition_variable::wait_until函数可以传入一个predicate,即一个用户自定义的判断是否符合条件的函数。这个也是很常见的模板编程的方法了。

  template<class_Clock, class_Duration>

  cv_status condition_variable::wait_until(unique_lock& __lk,

  constchrono::time_point& __t)

  {

  usingnamespacechrono;

  wait_for(__lk,__t- _Clock::now());

  return_Clock::now()

  bool condition_variable::wait_until(unique_lock& __lk,

  constchrono::time_point& __t,

  _Predicate __pred)

  {

  while(!__pred())

  {

  if(wait_until(__lk,__t)== cv_status::timeout)

  return__pred();

  }

  returntrue;

  }

  std::condition_variable_any

  std::condition_variable_any的接口和std::condition_variable一样,不同的是std::condition_variable只能使用std::unique_lock,而std::condition_variable_any可以使用任何的锁对象。

  下面来看下为什么std::condition_variable_any可以使用任意的锁对象。

  class_LIBCPP_TYPE_VIScondition_variable_any

  {

  condition_variable __cv_;

  shared_ptr __mut_;

  public:

  condition_variable_any();

  voidnotify_one()_NOEXCEPT;

  voidnotify_all()_NOEXCEPT;

  template<class_Lock>

  voidwait(_Lock& __lock);

  template<class_Lock, class_Predicate>

  voidwait(_Lock& __lock,_Predicate __pred);

  template<class_Lock, class_Clock, class_Duration>

  cv_status wait_until(_Lock& __lock,

  constchrono::time_point& __t);

  template<class_Lock, class_Clock, class_Duration, class_Predicate>

  bool wait_until(_Lock& __lock,

  constchrono::time_point& __t,

  _Predicate __pred);

  template<class_Lock, class_Rep, class_Period>

  cv_status wait_for(_Lock& __lock,

  constchrono::duration& __d);

  template<class_Lock, class_Rep, class_Period, class_Predicate>

  bool wait_for(_Lock& __lock,

  constchrono::duration& __d,

  _Predicate __pred);

  };

  可以看到,在std::condition_variable_any里,用shared_ptr __mut_来包装了mutex。所以一切都明白了,回顾std::unique_lock,它包装了mutex,当析构时自动释放mutex。在std::condition_variable_any里,这份工作让shared_ptr来做了。

  因此,也可以很轻松得出std::condition_variable_any会比std::condition_variable稍慢的结论了。

  其它的东东:

  sched_yield()函数的man手册:

  sched_yield() causes the calling thread to relinquish the CPU. The thread is moved to the end of the queue for its

  static priority and a new thread gets to run.

  在C++14里还有std::shared_lock和std::shared_timed_mutex,但是libc++里还没有对应的实现,因此不做分析。

  总结

  llvm libc++中的各种mutex, lock, condition variable实际上是封闭了posix里的对应实现。封装的技巧和一些细节值得细细推敲学习。

  看完了实现源码之后,对于如何使用就更加清晰了。

  参考:

  https://en.cppreference.com/w/cpp

  https://libcxx.llvm.org/

关注「CPP开发者」

看更多精选C/C++技术文章

↓↓↓

mt.sohu.com true cpp开发者mp https://mt.sohu.com/20161012/n470122510.shtml report 41713 (点击上方公众号,可快速关注)来源:hengyunabc链接:https://blog.csdn.net/hengyunabc/article/details/3
阅读(0) 举报
欢迎举报抄袭、转载、暴力色情及含有欺诈和虚假信息的不良文章。

热门关注

搜生活

搜生活+关注

搜狐公众平台官方账号

MAGIC杨梦晶

MAGIC杨梦晶+关注

生活时尚&搭配博主 /生活时尚自媒体 /时尚类书籍作者

搜狐教育

搜狐教育+关注

搜狐网教育频道官方账号

星吧GEO

星吧GEO+关注

全球最大华文占星网站-专业研究星座命理及测算服务机构

热门图片

  • 热点视频
  • 影视剧
  • 综艺
  • 原创
锦绣缘

同步热播-锦绣缘

主演:黄晓明/陈乔恩/乔任梁/谢君豪/吕佳容/戚迹
神雕侠侣

大结局-神雕侠侣

主演:陈晓/陈妍希/张馨予/杨明娜/毛晓彤/孙耀琦
封神英雄榜

同步热播-封神英雄榜

主演:陈键锋/李依晓/张迪/郑亦桐/张明明/何彦霓

六颗子弹

主演:尚格·云顿/乔·弗拉尼甘/Bianca Bree
龙虎少年队2

龙虎少年队2

主演:艾斯·库珀/ 查宁·塔图姆/ 乔纳·希尔

《奔跑吧兄弟》

baby14岁写真曝光

《我看你有戏》

李冰冰向成龙撒娇争宠

《明星同乐会》

李湘遭闺蜜曝光旧爱

《非你莫属》

美女模特教老板走秀

《一站到底》

曝搬砖男神奇葩择偶观

搜狐视频娱乐播报

柳岩被迫成赚钱工具

大鹏嘚吧嘚

大屁小P虐心恋

匆匆那年第16集

匆匆那年大结局

隐秘而伟大第二季

乔杉遭粉丝骚扰

The Kelly Show

男闺蜜的尴尬初夜

我来说两句排行榜

客服热线:86-10-58511234

客服邮箱:kf@vip.sohu.com