C++ 多线程 线程安全队列设计

news2025/1/13 7:46:09

这是看《C++并发编程实战》这本书学的,这里我要为这本书辟谣一下,虽然是这本书前面翻译得很烂,但是从第6章开始,应该是换了个人翻译,虽然还是能难懂,但是难懂的是代码逻辑,而不是语言逻辑;

实现,我先说明一下我自己的一个感悟,即对大多数线程错误的感悟:

1:我设定一个“信息有效期”这个概念,这个概念是值我从一个信息源获取一个信息,这个信息相对这个信息源是正确的时间段,比如一个线程获取一个队列是否为空,如果得到的结果是true,那么从获取这个信息到队列中添加一个元素这段时间就是获取到的信息的有效期;

2:大多数线程错误都是来源于信息有效期已经过去,我们知道,算法他的每一个步骤都是依赖之前的步骤的,如果前一个步骤的结果到实现依赖这个步骤的步骤时是错误的,那么不能说绝对,只能说是很可能这个步骤执行完后的结果也是错误的,那么这个“错误链”就会传递下去,如果没有正确的异常处理,那么这个程序很可能就会崩溃;

3:怎么避免信息有效期过去?

        -:获得的信息有效期够长

        -:利用锁来将信息有效期延长;

        -:获得的信息正确与否不重要,我们只关系信息本身【这个也可能是无锁并发的实现思想之一】

4:细粒度,如果我们要一个多线程安全的数据结构效率高的话,我们一般是需要控制好锁保护的数据范围,一般来说,一个锁保护的数据越多,那么这个数据结构并发访问的效率就越低,相反则反,另外一个方面,当一个操作需要的锁越多,那么并发访问的效率就越低,相反则反;

粗粒度队列的设计

一般我们是怎么设计一个队列的呢,利用锁将单线程安全队列变为多线程安全队列?

直接用一个单链表就行了;

 我们只需要用两个互斥量来保护head和tail的是串行的即可;

template<typename T>
class queue
{
private:
	struct node
	{
		T data;
		std::unique_ptr<node> next;
		node(T data_) :
			data(std::move(data_))
		{}
	};
	std::unique_ptr<node> head; // 1
	node* tail; // 2
	mutex head_mutex, tail_mutex;
public:
	queue()
	{}
	queue(const queue& other) = delete;
	queue& operator=(const queue& other) = delete;
	std::shared_ptr<T> try_pop()
	{
		lock_guard<mutex> lock_g(head_mutex);
		if (!head){
			return std::shared_ptr<T>();
		}
		std::shared_ptr<T> const res(
			std::make_shared<T>(std::move(head->data)));
		std::unique_ptr<node> const old_head = std::move(head);
		head = std::move(old_head->next); // 3
		return res;
	}
	void push(T new_value)
	{
		lock_guard<mutex> lock_g(tail_mutex);
		lock_guard<mutex> lock_g(head_mutex);
		std::unique_ptr<node> p(new node(std::move(new_value)));
		node* const new_tail = p.get();
		if (tail){
			tail->next = std::move(p); // 4
		}
		else{
			head = std::move(p); // 5
		}
		tail = new_tail; // 6
	}
};

这里我们可以看到,每一个锁在保护数据的数量上,已经是非常少了【每一个锁只保护了一个节点,这已经是非常理想的状态了】,那么我们可以看到在try_pop操作当中,我们只依赖与tail节点,而在push操作当中,我们依赖head和tail两个节点,那么我们能不能进行修改,让push只依赖一个节点呢?答案是:能;

细粒度队列的设计

我们只需要一个虚拟节点即可,这个节点我们保证一直在队列的最后,且队列稳定的时候,tail恒定指向这个虚拟节点,这样的话,当我们push的时候,我们就可以不用依赖与head节点了,因为无论是空队列还是非空队列,在执行push操作的前后,head的指向都不变,这意味着我们可以不访问head节点;

我们在构造队列的时候,我们创建一个虚拟节点,然后让head和tail都指向这个节点,要稍微注意的是,判空条件不再是head==nullptr了,而是head.get()==tail;

看具体代码:

class Queue {
public:
	class node {
	public:
		shared_ptr<int> data;
		unique_ptr<node> next;
	};
private:
	unique_ptr<node> head;
	node* tail;
	mutex tail_mtx, head_mtx;

	unique_ptr<node> pop_head() {
		std::lock_guard<mutex> lock_g(head_mtx);
		if (head.get() == get_tail()) {
			return nullptr;
		}
		unique_ptr<node> old_head = std::move(head);
		head = std::move(old_head->next);
		return old_head;
	}
	node* get_tail() {
		std::lock_guard<mutex> lock_g(tail_mtx);
		return tail;
	}

public:
	Queue()
		:head(std::make_unique<node>()), tail(head.get()){}
	void push(int new_date) {
		shared_ptr<int> date = std::make_shared<int>(new_date);
		unique_ptr<node> new_tail = std::make_unique<node>();
		node* temp = new_tail.get();
		std::lock_guard<mutex> lock_g(tail_mtx);
		tail->data = date;
		tail->next = std::move(new_tail);
		tail = temp;
	}

	std::shared_ptr<int> try_pop() {
		unique_ptr<node> ptr = pop_head();
		return ptr == nullptr ? nullptr : ptr->data;
	}
};

那么好,这个时候这个队列的并发访问性已经是非常高了【主要是队列能够操作的自由度是真不高】

那么好,我们现在再提出一个需求,有的时候,我们一个线程必须要从一个队列中获取一个元素,才能进行下面的操作【比如线程池】,而我们不希望用一个while循环来进行检查队列中是否有元素以供获取,这个时候我们可以通过环境变量来进行,但是我们希望这个wait的过程与实际的业务代码是低耦合的,所以我们要把,这个逻辑封装到队列中;

这里我们可以通过两种方式来获取队列中的值,一个是用引用hook,一个是直接通过返回值来获取;

当然,我们用引用hook时,就不免会用到客户端程序员的带代码,移动拷贝结果的时候可能会报错,这个时候需要对具体的异常进行捕获,为了避免这个异常,我们设计节点用share_ptr来存储结果,因为share_ptr内部在创建对象的时候有异常处理,但是这样做的坏处是我们分配的结果必须存储到堆中,这为push时创建结果的过程增加了不少的时间开销;

下面是具体代码:

template<typename T>
class queue_plus {
private:
	class node {
	public:
		std::shared_ptr<T> date;
		std::unique_ptr<node> next;
	};
	mutex head_mtx, tail_mtx, size_mtx;
	unique_ptr<node> head;
	node* tail;
	condition_variable cond;
	
	node* get_tail() {
		std::lock_guard<mutex> lock_g(tail_mtx);
		return tail;
	}

	shared_ptr<T> pop_head() {
		if (head->get() == get_tail()) {
			return nullptr;
		}
		shared_ptr<T> date=std::move(head->date);
		head = std::move(head->next);
		return date;
	}

	unique_lock<std::mutex> wait_for_notify() {
		std::unique_lock<std::mutex> head_lock(head_mtx);
		cond.wait(head_lock, [&] {head.get() != get_tail(); });
		return head_lock;
	}

	void wait_for_date(T& value) {
		std::unique_lock<std::mutex> head_lock(wait_for_notify());
		shared_ptr<T> date = pop_head();
		if (date != nullptr) {
			value = std::move(*date);
		}
	}
	shared_ptr<T> wait_for_date() {
		std::unique_lock<std::mutex> head_lock(wait_for_notify());
		return pop_head();
	}
public:

	void wait_and_pop(T& vlaue) {
		wait_for_date(value);
	}

	shared_ptr<T> wait_and_pop() {
		return wait_for_date();
	}

};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/19488.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL8.0优化 - 锁 - 全局锁、死锁

文章目录学习资料锁的不同角度分类锁的分类图如下其他锁之&#xff1a;全局锁其他锁之&#xff1a;死锁概念产生死锁的必要条件如何处理死锁如何避免死锁学习资料 【MySQL数据库教程天花板&#xff0c;mysql安装到mysql高级&#xff0c;强&#xff01;硬&#xff01;-哔哩哔哩…

Java——继承下的抽象类与接口

文章目录壹、抽象类贰、接口前言&#xff1a; 我们前一章写了继承性&#xff0c;这一章节&#xff0c;一起来学习Java中的抽象类与接口相关知识。 壹、抽象类 1、定义&#xff1a;用关键字abstract修饰的类称为抽象类&#xff08;abstract类&#xff09;。 2、格式&#xff1…

进程调度例题解析

文章目录例题1 一个四道作业的操作系统中&#xff0c;设在一段时间内先后到达6个作业&#xff0c;它们的提交时间和运行时间见表例题2 一个具有两道作业的批处理系统&#xff0c;作业调度采用短作业优先的调度算法&#xff0c;进程调度采用以优先数为基础的抢占式调度算法&…

智慧矿山解决方案-最新全套文件

智慧矿山解决方案-最新全套文件一、建设背景二、建设思路智能矿山建设存在的问题1、行业上存在的问题2、承建商存在的问题3、矿井自身存在的问题三、建设方案四、获取 - 智慧矿山全套最新解决方案合集一、建设背景 采矿业是我国国民经济的基础和支柱产业&#xff0c;而矿山智慧…

云安全防护总体架构设计

安全需求和挑战 从风险管理的角度讲&#xff0c;主要就是管理资产、威胁、脆弱性 和防护措施及其相关关系&#xff0c;最终保障云计算平台的持续安全&#xff0c;以及 其所支撑的业务的安全。 云计算 平台是在传统 IT技术的基础上&#xff0c;增加了一个虚拟化层&#xff0c;并…

MobaXterm工具使用/Docker安装Redis/Redisinsight工具使用

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

c++——map和set的使用

目录 一. 关联式容器 二. 键值对 三. 树形结构 Ⅰ. set 1. set的介绍 2. set的模版参数 3. set的构造 4. set的迭代器 5. set的容量 6. set其他操作 7. set的使用代码 Ⅱ. map 1. map的介绍 2. map的模板参数说明 3. map的构造 4. map的迭代器 5. map的容量 6…

Qt OpenGL(二十三)——Qt OpenGL 核心模式-给三角形上色

Qt OpenGL(二十三)——Qt OpenGL 核心模式-给三角形上色 本篇文章本来打算和上一篇文章写到一块的,但是考虑到VAO和VBO的概念是学习OpenGL核心模式绕不开的一个概念,所以单独再写一篇了,并且后面切换到使用Qt封装的VAO和VBO的时候,流程和这个也差不多,所以还是要做到理…

爬虫脚本代理池调度

爬虫脚本代理池调度 有时在使用爬虫或者使用脚本需要频繁访问一个网站&#xff0c;这种时候很容易被服务器给ban掉ip&#xff0c;这种情况就可以使用代理池。从代理池中进行调度获取新的ip进行访问。 使用的是开源免费的python项目地址如下&#xff1a; https://github.com/j…

【GPGPU编程模型与架构原理】第二章 2.1 计算模型

本章介绍以CUDA和OpenCL 并行编程中的一些核心架构概念来展示GPGPU的计算、编程和存储模型。本章还介绍虚拟指令集和机器指令集&#xff0c;逐步揭开GPGPU体系结构的面纱。 2.1 计算模型 计算模型是编程框架的核心&#xff0c;计算模型需要根据计算核心的硬件架构提取计算的共…

使用TinyPNG API压缩图片

使用TinyPNG API压缩图片 在撰写论文的时候&#xff0c;美观&#xff0c;大气&#xff0c;上档次的图标能够很好地给自己的论文加分&#xff0c;好的可视化结果也能够让审稿人赏心悦目。但是有时候在可视化图片的时候有可能原始图像过大从而很占内存&#xff1b;这时候就希望能…

Python+Numpy+CV2/GDAL实现对图像的Wallis匀色

Wallis匀色原理&#xff1a; # f(x,y)&#xff1a;Wallis匀色后结果 # g(x,y):输入的待匀色影像 # mg:待处理影像的灰度均值 # mf:参考影像的灰度均值 # sg:待处理影像和的标准偏差 # sf:参考影像的标准偏差 f(x,y)(g(x,y)−mg)⋅(sf/sg)mf匀色代码逻辑解释&#xff1…

从阿里云“数字证书管理服务”申请免费的SSL证书

最近网站的SSL证书即将到期&#xff0c;之前是从FreeSSL申请的证书&#xff0c;而且是通过OpenSSL自己生成CSR文件的方式申请的证书&#xff0c;操作还是比较繁琐。&#xff08;具体参考&#xff1a; https://blog.csdn.net/weixin_42534940/article/details/90745452 &#xf…

一、几种常用的设计模式

设计模式分类 创建者模式&#xff1a;对象实例化的模式&#xff0c;创建型模式用于解耦对象的实例化过程。 常用&#xff1a;单例模式、工厂方法模式、抽象工厂模式、建造者模式 。 不常用&#xff1a;原型模式结构型模式&#xff1a;把类或对象结合在一起形成一个更大的结构。…

Tilemap瓦片资源

1、Tilemap Tilemap一般称之为 瓦片地图或者平铺地图&#xff0c;是Unity2017中新增的功能&#xff0c;主要用于快速编辑2D游戏中的场景&#xff0c;通过复用资源的形式提升地图多样性 工作原理就是用一张张的小图排列组合为一张大地图 它和SpriteShape都是用于制作2D游戏的…

CEAC 之《企业信息化管理》1

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;微微的猪食小窝 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 微微的猪食小窝 原创 收录于专栏 【CEAC证书】 1综合布线是智能建筑的信息高速公路。 A、正确 B、错误A2直通线的一根双绞线的两端执行不同…

Java基础实战项目-------网上订餐系统

目录 前言 项目需求 项目环境准备 技能点 实现思路 ​编辑 项目总结 完整代码&#xff1a; 前言 已学完Java基础部分的内容&#xff0c;如下 理解程序的基本概念&#xff1a;程序、变量、数据类型 会使用顺序、选择、循环、跳转语句编写程序 会使用数组以及Arrays的…

[附源码]SSM计算机毕业设计智慧教学平台JAVA

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

[附源码]java毕业设计生产型企业员工管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

应急响应-账户排查

用户信息排查 在服务器被入侵之后&#xff0c;攻击者可能会建立相关账户&#xff0c;方便进行远程控制。 主要采用一下几种&#xff1a; 直接建立一个新用户&#xff1b;(有时候为了混淆视听&#xff0c;账户名称和系统常用名相似)激活一个系统中的默认用户&#xff0c;但是这…