• C++ 多线程 线程安全关联性map设计


    我们知道关联性map有多种实现方式,一般有顺序表和二叉树以及哈希表

    如果我们要设计一个线程安全的map,且要求并发性要高,那么我们可以对上面的实现方法做一个评价;

    1:二叉树,稍微想一下就会不合适,拿AVL树来讲

            :效率底下,每次访问树都需要经过根节点,这说明我们要拿一个互斥量来保护根节点,这很不好,因为访问根节点的次数太多了;

            :容易死锁,当有多个线程进行着向下遍历树和向上遍历数,因为每次遍历可能需要获取邻近节点的锁,所以遍历方向的不同会导致死锁的发声

    【这些毛病是树结构通有的,因为树型数据结构每个数据的之间的依赖性太强了,这说明树型结构可能不适合多线程并发】

    2:顺序表,这个数据结构有着比二叉树更操蛋的问题

            :会导致死锁,和二叉树一样的理由

            :效率底下,不是因为互斥量的原因,而是因为数据结构本身的原因,一次查询就需要o(n)的时间,因为这个,即使在单线程中,也几乎没有人会拿顺序表作为map的实现

    3:哈希表,这个数据结构就非常适合并发了,而且是高并发!

            :对于每一个桶之间都是一个独立的个体,之间没有依赖关系,这说明我们可以同时访问两个桶,更普遍地说,如果情况是理想的,我们可以同时访问所有桶!只需要给每个桶一个互斥量,以保证对单独的一个桶的访问是串行的【我们也可以用boost::shared_lock来做到对一个桶多并发读访问,但是修改访问是串行的】

    综上所述,我选择哈希表作为map的实现:

    看下列代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. using namespace std;
    10. //类拷贝方案错误
    11. class class_construct_error :public exception {};
    12. template<typename key,typename value,typename hash=std::hash>
    13. class hash_map {
    14. private:
    15. class bucket {
    16. private:
    17. typedef pair date;
    18. typedef typename std::list::iterator date_iterator;
    19. typedef std::list date_store;
    20. //拷贝策略接口:
    21. struct strategy_construct {
    22. virtual void operator()(date_iterator it, const value& val) = 0;
    23. };
    24. //两种策略拷贝策略
    25. //移动拷贝策略
    26. struct move_construct :public strategy_construct {
    27. void operator()(date_iterator it, const value& val) {
    28. it->second = std::move(val);
    29. }
    30. };
    31. //赋值拷贝策略
    32. struct copy_construct :public strategy_construct {
    33. void operator()(date_iterator it, const value& val) {
    34. it->second = val;
    35. }
    36. };
    37. date_store dates;
    38. std::mutex mtx;
    39. //寻找迭代器
    40. date_iterator find_for_iterator(const key& k) {
    41. return find_if(dates.begin(), dates.end(),
    42. [&](const date& it)
    43. {return k == it.first; }
    44. );
    45. }
    46. public:
    47. strategy_construct *strategy;
    48. bucket() {
    49. if (std::is_move_constructible::value) {
    50. strategy = new move_construct();
    51. return;
    52. }
    53. else if (std::is_copy_constructible::value) {
    54. strategy = new copy_construct();
    55. return;
    56. }
    57. //如果类没有合适的拷贝构造函数,那么就直接中止程序
    58. }
    59. //修改、添加值
    60. //如果添加,返回true,否则返回false
    61. bool add_or_update(const key& k, const value& val) {
    62. lock_guard lock_g(mtx);
    63. date_iterator it = find_for_iterator(k);
    64. if (it == dates.end()) {//没找到,则添加值
    65. dates.push_back(date(k, val));
    66. return true;
    67. }
    68. else {
    69. (*strategy)(it, val);//要求value类要有拷贝构造函数
    70. return false;
    71. }
    72. }
    73. /* 删除值
    74. * return 如果值存在并删除,那么return true 否则 return false
    75. */
    76. bool remove(const key& k) {
    77. lock_guard lock_g(mtx);
    78. date_iterator it = find_for_iterator(k);
    79. if (it != dates.end()) {
    80. dates.erase(it);
    81. return true;
    82. }
    83. return false;
    84. }
    85. /*寻找k对应的值
    86. return 如果找到,则return对应的value,否则return 默认val
    87. */
    88. value find_value(const key& k, const value& default_val) {
    89. lock_guard lock_g(mtx);
    90. date_iterator it = find_for_iterator(k);
    91. if (it != dates.end()) {
    92. return it->second;
    93. }
    94. return default_val;
    95. }
    96. size_t size() {
    97. return dates.size();
    98. }
    99. //这三个操作是为扩充哈希表而实现的,在这段时间内任何对哈希表的请求都会被阻塞,所以不需要锁保护
    100. void push(date& new_date) {
    101. this->dates.push_back(std::move(new_date));
    102. }
    103. date_iterator begin() {
    104. return dates.begin();
    105. }
    106. date_iterator end() {
    107. return dates.end();
    108. }
    109. };
    110. std::vector> buckets;//桶管理器
    111. hash hasher;//哈希器
    112. size_t cnt;//元素个数
    113. atomic<bool> flag{ 1 };//是否将刚调用哈希表的线程阻塞
    114. atomic<size_t> consumer;//当前时刻访问的哈希表的线程量
    115. condition_variable cond_check;//让check函数等待
    116. condition_variable cond_enter;//让正在扩充哈希表的这段时间内访问哈希表的线程等待
    117. std::mutex mtx_check, mtx_enter;
    118. bucket& find_bucket(const key& k) {
    119. std::size_t const bucket_index = hasher(k) % buckets.size();
    120. return *buckets[bucket_index];
    121. }
    122. void reorder() {
    123. size_t old_size = buckets.size();
    124. size_t index;
    125. std::vector> new_bucket(old_size * 10);
    126. for (auto& t : new_bucket) {
    127. t = std::make_unique();
    128. }
    129. for (auto& t : buckets) {
    130. for (auto it = t.get()->begin(); it != t.get()->end(); it++) {
    131. index = hasher(it->first) % new_bucket.size();
    132. new_bucket[index].get()->push(*it);
    133. }
    134. }
    135. buckets = std::move(new_bucket);
    136. //哈希表扩充完毕,唤醒等待的线程;
    137. flag = 1;
    138. cond_enter.notify_all();
    139. }
    140. void check() {
    141. if (double(cnt) / buckets.size() > 16) {
    142. if (consumer > 0) {
    143. flag=false;//开始扩充哈希表,阻止线程访问
    144. unique_lock lock_u(mtx_check);
    145. cond_check.wait(lock_u);//等待所有线程从哈希表中退出
    146. }
    147. flag = false;
    148. reorder();
    149. }
    150. }
    151. void enter() {
    152. if (flag == false) {
    153. unique_lock lock_u(mtx_enter);
    154. cond_enter.wait(lock_u);
    155. lock_u.unlock();//记得要unlock,要不然这些线程会串行
    156. }
    157. consumer++;
    158. }
    159. void exit() {
    160. consumer--;
    161. if (flag == 0 && consumer == 0) {
    162. //此刻哈希表的访问线程为0,哈希表正在等待扩充
    163. cond_check.notify_all();//开始扩充
    164. }
    165. }
    166. public:
    167. hash_map(unsigned int size = 19)
    168. :buckets(size) {
    169. for (unsigned int i = 0; i < size; i++) {
    170. buckets[i].reset(new bucket());
    171. }
    172. }
    173. hash_map(const hash_map&) = delete;
    174. value find(const key& k, const value& default_val = value()) {
    175. enter();
    176. return this->exit(),find_bucket(k).find_value(k, default_val);
    177. }
    178. //如果添加,返回true,否则返回false
    179. bool modify_or_add(const key& k,const value& val) {
    180. enter();
    181. if (find_bucket(k).add_or_update(k, val)) {
    182. this->exit();
    183. cnt++; check();
    184. return true;
    185. }
    186. else {
    187. this->exit();
    188. return false;
    189. }
    190. }
    191. bool remove(const key& k) {
    192. enter();
    193. if (find_bucket(k).remove(k)) {
    194. cnt--;
    195. this->exit();
    196. return true;
    197. }
    198. else {
    199. this->exit();
    200. return false;
    201. }
    202. }
    203. size_t size() { return cnt; }
    204. size_t bucket_size() { return buckets.size(); }
    205. };

    这边的思路是用一个互斥量来保护一个桶,这里没什么好说的;

    但是要注意,当我们的哈希表扩张时,要保证此刻哈希表没有线程进行访问,且要保证在进行扩展的这段时间,线程能够进行访问的,为此我利用了两个环境变量和两个原子对象来控制这个流程,下面简单描述一下逻辑;

    atmoic consumer;此刻访问哈希表的线程数

    atomic flag;是否允许线程对哈希表进行访问,当开始扩充时,将其设置为false

    1:当一个线程开始访问哈希表时,先进入enter函数,判断是否能进行访问,如果不能则线程进入阻塞状态;

    2:当一个线程结束访问哈希表是,进入exit函数,判断是否能哈希表是否能进行访问,如果不能,则说明哈希表正在等待扩张,这再判断此刻是否已经没有线程对哈希表进行访问,如果是的话,则唤醒哈希表,进行扩张;

    3:当要开始扩张哈希表的时候,见flag置为false,并进入阻塞状态,等待线程将其唤醒;

    4:当扩张完毕时,将flag设置为true,以开放哈希表,然后唤醒在扩展时进入阻塞状态的线程;

  • 相关阅读:
    情态动词习题
    个人博客系统的总结
    mysql binlog数据恢复
    基于Nodejs的外卖点餐平台的设计和实现
    案例分享 | 基于ETest平台开发某型DCS测试系统
    [实践篇]13.5 QNX侧如何操作进程?
    ES7~11学习48~68
    采访 Footprint Analytics CEO Navy:AI 与 Web3 的融合之道
    SpringCloud -- Nacos配置管理
    java基于ssm大学生社团管理系统-计算机毕业设计
  • 原文地址:https://blog.csdn.net/weixin_62953519/article/details/127942046