C++并发编程(二十八)

  • 前言
  • 一、线程安全的链表结构
  • 总结

前言

链表也是以节点套节点的方式连接而成的经典数据结构,对于并发数据结构,链表有几个问题需要解决。

首先,链表不能双向访问,以前的文章我们探讨过,对于一个可以从两个方向访问的链表,会最终在某一个时刻碰到同一个数据,并且一路自两个方向延续的加锁可能引发死锁。

其次,无法使用迭代器,因为对于一个随时可能被其它线程改变的链表,不能保证迭代器的有效。


一、线程安全的链表结构

前言中,我们探讨了为了并发的安全我们只能让链表单一顺序访问,同时不用迭代器。

于是为了细颗粒度的并发性能,需要每个节点都带锁,当我们顺序访问链表时,需要逐个的加锁,开锁。

于是就有了一个问题,当一个线程持有链表中断的某个节点,其余节点不能被越过去读取,这在使用中会造成一定的问题,需要注意。

#include <algorithm>
#include <iostream>
#include <list>
#include <memory>
#include <shared_mutex>
#include <vector>namespace threadSafe
{template <typename T>
struct threadSafeList;template <typename T>
struct node
{// 默认构造node() = default;// 构造函数explicit node(const T &value): data(std::make_shared<T>(value)){}private:std::mutex m;// 智能指针包裹的数据std::shared_ptr<T> data;// 智能指针包裹的 node 指针std::unique_ptr<node<T>> next; // = nullptr;friend struct threadSafeList<T>;
};template <typename T>
struct threadSafeList
{// 默认构造threadSafeList() = default;// 析构函数~threadSafeList(){removeIf([](T &) { return true; });}// 禁用拷贝构造threadSafeList(const threadSafeList &other) = delete;// 禁用拷贝赋值auto operator=(const threadSafeList &other) -> threadSafeList & = delete;// 自头节点插入值void pushFront(const T &value){// 构造有值节点std::unique_ptr<node<T>> newNode(new node<T>(value));// 上头节点锁std::lock_guard<std::mutex> const headLock(head.m);// 新节点指向原头节点指向的节点newNode->next = std::move(head.next);// 头节点指向新节点,头节点永远不存储值head.next = std::move(newNode);}// 根据用户传入的函数处理节点中的每个数据template <typename Function>void forEach(Function func){// 获取头节点指针node<T> *current = &head;// 头节点上锁std::unique_lock<std::mutex> headLock(head.m);// 获取next节点,如果为空,结束循环while (node<T> *next = current->next.get()){// next 节点上锁std::unique_lock<std::mutex> nextLk(next->m);// 当前节点解锁headLock.unlock();// 处理后节点数据func(*next->data);// 当前指针后移一个节点current = next;// 后一个节点锁转移给当前锁headLock = std::move(nextLk);}}// 查找第一个符合用户传递的判断函数的数据template <typename Predicate>auto findFirstIf(Predicate predicateFun) -> std::shared_ptr<T>{node<T> *current = &head;std::unique_lock<std::mutex> headLock(head.m);while (node<T> *next = current->next.get()){std::unique_lock<std::mutex> nextLk(next->m);headLock.unlock();// 根据判断函数寻找值if (predicateFun(*next->data)){return next->data;}current = next;headLock = std::move(nextLk);}return std::shared_ptr<T>();}// 移除符合用户传入的判断函数的值template <typename Predicate>void removeIf(Predicate predicateFun){node<T> *current = &head;std::unique_lock<std::mutex> headLock(head.m);while (node<T> *next = current->next.get()){std::unique_lock<std::mutex> nextLk(next->m);// 如果节点值符合判断函数则予以删除if (predicateFun(*next->data)){// 获取当前节点的下一个节点std::unique_ptr<node<T>> const oldNext =std::move(current->next);// 删除下一个节点,也就是当前节点指向下下个节点// 上一步获得的要被删除的 oldNext 节点,会自动析构,// 包裹的数据会因智能指针的消除而消除current->next = std::move(next->next);// 释放锁,以便顺利析构,因为 nextLk 的析构要晚于 oldNextnextLk.unlock();}else{headLock.unlock();current = next;headLock = std::move(nextLk);}}}private:node<T> head;
};} // namespace threadSafeauto main() -> int
{threadSafe::threadSafeList<int> thrdSfLst;thrdSfLst.pushFront(10);thrdSfLst.pushFront(1);auto rslt = thrdSfLst.findFirstIf([](int value) { return value == 1; });thrdSfLst.forEach([](int value) { std::cout << value << std::endl; });thrdSfLst.removeIf([](int value) { return value == 10; });return 0;
}

总结

在极度简化接口的情况下,基本处于可用,难点在于细颗粒度的传递节点及开关锁。

2022-09-29 C++并发编程(二十八)相关推荐

  1. 【并发编程二十】协程(coroutine)_协程库

    [并发编程二十]协程(coroutine) 一.线程的缺点 二.协程 三.优点 四.个人理解 五.协程库 1.window系统 2.unix系统(包括linux的各个版本) 2.1.makeconte ...

  2. java并发编程(二十六)——单例模式的双重检查锁模式为什么必须加 volatile?

    前言 本文我们从一个问题出发来进行探究关于volatile的应用. 问题:单例模式的双重检查锁模式为什么必须加 volatile? 什么是单例模式 单例模式指的是,保证一个类只有一个实例,并且提供一个 ...

  3. 聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁

    这篇讲讲ReentrantReadWriteLock可重入读写锁,它不仅是读写锁的实现,并且支持可重入性. 聊聊高并发(十五)实现一个简单的读-写锁(共享-排他锁) 这篇讲了如何模拟一个读写锁. 可重 ...

  4. 聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁

    上一篇聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁 讲了可重入读写锁的基本情况和主要的方法,显示了如何 ...

  5. 聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁...

    上一篇聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁 讲了可重入读写锁的基本情况和基本的方法,显示了怎样 ...

  6. 学习ASP.NET Core Razor 编程系列十八——并发解决方案

    原文:学习ASP.NET Core Razor 编程系列十八--并发解决方案 学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP. ...

  7. JUC并发编程第十四篇,StampedLock(邮戳锁)为什么比ReentrantReadWriteLock(读写锁)更快!

    JUC并发编程第十四篇,StampedLock(邮戳锁)为什么比ReentrantReadWriteLock(读写锁)更快! 一.ReentrantReadWriteLock(读写锁) 1.读写锁存在 ...

  8. 聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore

    前几篇分析了一下AQS的原理和实现,这篇拿Semaphore信号量做例子看看AQS实际是如何使用的. Semaphore表示了一种可以同时有多个线程进入临界区的同步器,它维护了一个状态表示可用的票据, ...

  9. CSDN 编程竞赛二十八期题解

    竞赛总览 CSDN 编程竞赛二十八期:比赛详情 (csdn.net) 本期竞赛的题目都很简单,但是非常考验读题和编码速度.这一次没有遇到bug,竞赛体验较好. 竞赛题解 题目1.小Q的鲜榨柠檬汁 团建 ...

  10. 2021年安全生产工作总结及2022年思路计划(二十八篇)PPTX(附下载)

    摘要:2021年安全生产工作总结及2022年思路计划(二十八篇) 公众号:安全生产星球

最新文章

  1. Android零基础入门第89节:Fragment回退栈及弹出方法
  2. cocos2d-x初探学习笔记(18)--Lable
  3. win7中输入文件夹首字母跳到相应的文件或者文件夹,却在搜索栏出现输入的字母...
  4. 没有足够的值_元丰通宝值多少钱吗?市场价值如何?有没有足够的收藏空间?...
  5. es6 super关键字
  6. 七阶拉丁方阵_【C语言】输出N阶拉丁方阵并统计个数
  7. 二进制“==”: 没有找到接受“Point”类型的左操作数的运算符(或没有可接受的转换)
  8. ImageLoader的简单分析(终结篇)
  9. PHP下ereg实现匹配ip的正则
  10. JavaScript的注释
  11. c语言变量常量知识点,嵌入式C语言之变量与常量详解
  12. [Unity][摄像机视角]多个摄像机之间切换
  13. 史上最牛最强的linux学习笔记 4.linux常用命令
  14. js学习笔记----JavaScript中DOM扩展的那些事
  15. Win10设置Win+R快捷方式或保存Path路径消失无效
  16. 转贴:华为加班死人了
  17. oracle 12c 安装scott,Oracle 12c中添加scott用户的方法
  18. hd disk / HDD / SSD / USB / FireWire(1394) / eSATA / SATA / mSATA / NGFF
  19. uni-app学习(一)
  20. 使用Python获取股票解禁数据并绘制股价曲线

热门文章

  1. 2021年化工自动化控制仪表新版试题及化工自动化控制仪表找解析
  2. layaair的TS版本报错问题
  3. 十一青岛2人4日自助游记
  4. 学习uc/os-ii
  5. arduino中print()跟write()最大区别和注意事项 (最详细)
  6. CLM陆面过程模式实践技术应用
  7. c语言 绝对值比较大小,c语言-求绝对值最大值
  8. php android 微信登录,Android_Android 实现微信登录详解,前言分享到微信朋友圈 - phpStudy...
  9. 「CF1154F」Shovels Shop【背包DP】
  10. 【综述笔记】一些弱监督语义分割论文