当前位置:   article > 正文

百度自动驾驶apollo源码解读1:std::atomic实现读写锁_百度的源代码

百度的源代码

直接上源代码吧,代码是阿波罗团队写的源代码,我这边给加了注释

  1. /*
  2. 1.有一说一这个读写锁的设计还是很牛逼的,以我自己感觉atomic和读写完全不相干的东西竟然可以用前者实现后者
  3. 2.这个设计理念是不是百度阿波罗团队自创的呢?好像不是,这有个连接,设计理念很相似,15年的博客:
  4. https://blog.csdn.net/10km/article/details/49641691
  5. */
  6. #ifndef CYBER_BASE_ATOMIC_RW_LOCK_H_
  7. #define CYBER_BASE_ATOMIC_RW_LOCK_H_
  8. #include <unistd.h>
  9. #include <atomic>
  10. #include <condition_variable>
  11. #include <cstdint>
  12. #include <cstdlib>
  13. #include <iostream>
  14. #include <mutex>
  15. #include <thread>
  16. #include "rw_lock_guard.h"
  17. namespace apollo
  18. {
  19. namespace cyber
  20. {
  21. namespace base
  22. {
  23. class AtomicRWLock::
  24. {
  25. //声明两个友元类,这两个类主要是利用C++的RAII机制对加锁和开锁的封装
  26. //这两个类主要调用本类的四个privte接口:ReadLock,WriteLock,ReadUnlock,WriteUnlock
  27. //就像C11中的std::mutex和std::lock_guand之间的关系
  28. friend class ReadLockGuard<AtomicRWLock>;
  29. friend class WriteLockGuard<AtomicRWLock>;
  30. public:
  31. // RW_LOCK_FREE和WRITE_EXCLUSIVE都是表示锁的状态,RW_LOCK_FREE表示目前没人占用的意思,WRITE_EXCLUSIVE则表示当前锁被一个写的操作占用
  32. //这个时候你可能会有疑惑,为啥没有“在读”状态呢?在这里在读是和一个正整数表示的,比如1就表示一个线程在读,2就不是两个线程在读
  33. //这个时候你可能会还有疑惑,人家状态一般都是用枚举表示,或者宏定义,正因为上述读状态是不确定正整数,这个状态用整数表示稳妥
  34. static const int32_t RW_LOCK_FREE = 0;
  35. static const int32_t WRITE_EXCLUSIVE = -1;
  36. static const uint32_t MAX_RETRY_TIMES = 5; //尝试获取锁的时候连续尝试次数,就像自旋锁那样,连续失败MAX_RETRY_TIMES次则会让出线程的执行权
  37. AtomicRWLock() {}
  38. explicit AtomicRWLock(bool write_first) : write_first_(write_first) {}
  39. private:
  40. // all these function only can used by ReadLockGuard/WriteLockGuard;
  41. //下面4个接口是给辅助类ReadLockGuard和WriteLockGuard来调用的
  42. void ReadLock();
  43. void WriteLock();
  44. void ReadUnlock();
  45. void WriteUnlock();
  46. AtomicRWLock(const AtomicRWLock &) = delete; //删除拷贝函数和辅助函数,没啥说的,常规操作
  47. AtomicRWLock &operator=(const AtomicRWLock &) = delete;
  48. std::atomic<uint32_t> write_lock_wait_num_ = {0}; //等待拿到锁的写操作的个数(肯定是非负整数)
  49. std::atomic<int32_t> lock_num_ = {0}; //锁当前的状态,对标上述提到的RW_LOCK_FREE和WRITE_EXCLUSIVE,说实话这个当时困扰了我好久,状态就状态呗,你起个名字叫num,
  50. //让人联想到锁的个数,或者说次数,但是你仔细看他的类型是int32_t,这意味着他可能是个负数
  51. //看源代码咋都联系不到一块儿,几乎到了放弃抵抗,
  52. bool write_first_ = true;//表示优先干啥。假设现在锁被写操作A占用,此时又来了读操作B,过了极短时间又来了写操作C,B和C现在都想获得锁,一旦A释放了锁,别看B是先来的,要是
  53. //设置了write_first_=ture则依然是C优先拿到锁资源
  54. };
  55. //看ReadLock之前建议先看WriteLock,后者比较简单,由浅到深比较容易理解和接受
  56. inline void AtomicRWLock::ReadLock()
  57. {
  58. uint32_t retry_times = 0;
  59. int32_t lock_num = lock_num_.load();//读取锁状态,这个时候你可能会问WriteLock咋就没读呢?compare_exchange_weak就是包含读的操作
  60. if (write_first_)//是否优先写锁舔狗上位机会
  61. {
  62. do
  63. {
  64. //仔细比对,write_first_的区别就是下面一行的write_lock_wait_num_.load() > 0判断。翻译为:看看舔狗队列中有没有存在写锁的,有的话自己身为读锁就继续循环,再等等
  65. while (lock_num < RW_LOCK_FREE || write_lock_wait_num_.load() > 0)
  66. {
  67. if (++retry_times == MAX_RETRY_TIMES)
  68. {
  69. // saving cpu
  70. std::this_thread::yield();
  71. retry_times = 0;
  72. }
  73. lock_num = lock_num_.load();
  74. }
  75. //程序能走到这里,就是意味着lock_num>=0 且 write_lock_wait_num_==0
  76. //下面两个情况:
  77. //1.lock_num_==lock_num(即大于等于0),可能你会说这不是肯定的吗?这个还真不一定,虽然刚执行过lock_num = lock_num_.load();但是lock_num是个多线程控制的值,随时在变
  78. //接着说情况1,lock_num_==lock_num lock_num_赋值加+1 返回true 循环结束
  79. // 情况2,lock_num_!=lock_num lock_num赋值为lock_num_(无用) 重新循环 简单点来讲就是:刚刚的判断好好的,正当我要办事的时候,突然有线程偷偷改变了值,一切判断作废,重新来过
  80. } while (!lock_num_.compare_exchange_weak(lock_num, lock_num + 1,
  81. std::memory_order_acq_rel,
  82. std::memory_order_relaxed));
  83. }
  84. else
  85. {
  86. do
  87. {
  88. while (lock_num < RW_LOCK_FREE)
  89. {
  90. if (++retry_times == MAX_RETRY_TIMES)
  91. {
  92. // saving cpu
  93. std::this_thread::yield();
  94. retry_times = 0;
  95. }
  96. lock_num = lock_num_.load();
  97. }
  98. } while (!lock_num_.compare_exchange_weak(lock_num, lock_num + 1,
  99. std::memory_order_acq_rel,
  100. std::memory_order_relaxed));
  101. }
  102. }
  103. //看WriteLock之前建议先看ReadUnlock和WriteUnlock,更容易理解和接受lock_num_是干啥的
  104. inline void AtomicRWLock::WriteLock()
  105. {
  106. int32_t rw_lock_free = RW_LOCK_FREE;//用变量获取RW_LOCK_FREE的值,方便给compare_exchange_weak传递一个参数,
  107. uint32_t retry_times = 0;//连续尝试次数
  108. write_lock_wait_num_.fetch_add(1);//记录等待获取锁权限的写锁个数,为啥专门还记录一下呢?主要为了控制等待获取锁的先后顺序
  109. //简单点来讲,只要有写锁想要获得锁权限(尚未获得),你读锁就往后站站,等我先完活
  110. //下面这个循环分为下面两个情况:
  111. //lock_num_==rw_lock_free==0 现在没人用锁 lock_num_变为WRITE_EXCLUSIVE(即-1) 返回值true 循环结束
  112. //lock_num_!=rw_lock_free 又分为两种情况,情况1:lock_num_==WRITE_EXCLUSIVE 情况2:lock_num_>=1。无论情况1和情况2:
  113. // 现在有人用锁 lock_num_不变 rw_lock_free变为lock_num_(但是此时该值无用) 返回false 循环进入
  114. while (!lock_num_.compare_exchange_weak(rw_lock_free, WRITE_EXCLUSIVE,
  115. std::memory_order_acq_rel,
  116. std::memory_order_relaxed))
  117. {
  118. // rw_lock_free will change after CAS fail, so init agin
  119. rw_lock_free = RW_LOCK_FREE;//重置rw_lock_free,因为只要进来,rw_lock_free的值已经被改变,下次循环不好作标志
  120. if (++retry_times == MAX_RETRY_TIMES)//判断循环次数,
  121. {
  122. // saving cpu
  123. std::this_thread::yield();//到达指定次数,让出cpu执行权限,等待下次调用机会
  124. retry_times = 0;
  125. }
  126. }
  127. write_lock_wait_num_.fetch_sub(1);//走到这里意味着自己已经成功拿到了锁权限,而不是只是在等待机会的添狗,舔狗个数减1
  128. }
  129. //上面说过,一旦读锁拿到锁则意味着lock_num_是正整数,也就是+1,当他释放该锁的时候将lock_num_减去1很好理解
  130. inline void AtomicRWLock::ReadUnlock() { lock_num_.fetch_sub(1); }
  131. //上面说过,锁自由状态是0,被写锁占用时候状态是-1,写锁释放的时候加上1也很好理解,就是要从-1转到0嘛
  132. inline void AtomicRWLock::WriteUnlock() { lock_num_.fetch_add(1); }
  133. } // namespace base
  134. } // namespace cyber
  135. } // namespace apollo
  136. #endif // CYBER_BASE_ATOMIC_RW_LOCK_H_

头文件rw_lock_guard.h连接:https://gitee.com/ApolloAuto/apollo/tree/master/cyber/base

看了他的实现原理下面我们测试下他的性能和C++17的shared_mutex实现的读写锁对比一下

  1. //编译指令:g++ main.cpp -lpthread -std=c++17
  2. //运行指令:./a.out
  3. #include <iostream>
  4. #include "atomic_rw_lock.h"
  5. #include <mutex>
  6. #include <shared_mutex>
  7. using namespace apollo::cyber::base;
  8. using namespace std;
  9. int64_t i = 0;
  10. int64_t s = 0;
  11. int64_t count = 1*1000*1000;
  12. //c17
  13. typedef std::shared_lock<std::shared_mutex> read_lock;
  14. typedef std::unique_lock<std::shared_mutex> write_lock;
  15. std::shared_mutex sm;
  16. //cyber
  17. AtomicRWLock l;
  18. void fun1()
  19. {
  20. for (int c=0; c<count; c++)
  21. {
  22. WriteLockGuard<AtomicRWLock> w(l);
  23. //write_lock w(sm);
  24. i++;
  25. }
  26. }
  27. void fun2()
  28. {
  29. for (int c=0; c<count; c++)
  30. {
  31. ReadLockGuard<AtomicRWLock> r(l);
  32. //read_lock r(sm);
  33. s = s + i;
  34. }
  35. }
  36. int main()
  37. {
  38. auto beforeTime = std::chrono::steady_clock::now();
  39. std::thread t1(fun1);
  40. std::thread t2(fun1);
  41. std::thread t3(fun2);
  42. t1.join();
  43. t2.join();
  44. t3.join();
  45. std::cout << " i : " << i << std::endl;
  46. std::cout << " s : " << s << std::endl;
  47. auto afterTime = std::chrono::steady_clock::now();
  48. std::cout << "总耗时:" << std::endl;
  49. double duration_millsecond = std::chrono::duration<double, std::milli>(afterTime - beforeTime).count();
  50. std::cout << duration_millsecond << "毫秒" << std::endl;
  51. return 0;
  52. }

整体感觉性能方面差不多,那为啥他们不使用C++17的读写锁,偏偏使用atomic实现的呢?阿波罗3.5版本才有的,时间大约在2019年前后,那个时候C++2017标准肯定有了的。不知道,先做记录吧

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/414102
推荐阅读
相关标签
  

闽ICP备14008679号