一步一步写线程之八线程池的完善之二数据结构封装
一、数据容器
在前面分析过,不管是线程任务的封装还是同步数据队列的封装,都是需要一个数据结构的。一用来说,如果没有什么特殊的原因,开发者都是使用STL中数据结构。比如前面经常见到的std::queue,std::deque,std::vector,std::unordered_map,std::list等等。
 但是大家都知道,标准库中的容器一般都是非线程安全的,所以在线程池中如果想使用这些容器,就需要自己处理一下相关的线程安全相关的控制。
二、线程的安全性
所谓线程的安全数据访问,其实在前面学习了很多方法和技术。在不同的平台上,可能也有不小的差异。但标准库的出现还是让这种差异弥合了很多。一般来说,在保证线程数据安全访问,也就是同步控制上,使用锁。也就是lock来锁住mutex。
 但如何安全的使用锁,这就有很多技巧了。包括锁的粒度的大小,锁使用的时机和应用的地方。在下面的例子中,大家可以分析一下,如何进行更好的优化。
三、接口封装
数据结构如果使用标准库的容器,就会有一个问题,标准库的不同的容器,其对外的接口的名称是不相同的。当然,如何不对这些容器进行泛化,只是单纯的使用某种容器,就没有这种问题。但是如何想使用模板来整体使用这些容器呢?
 这就需要一些辅助的技术来控制,既要达到在上层的接口统一性,又要保证在调用下层的库接口时的安全。这时,就用到了模板的SFINAE技术,当然,在高版本中,也可以使用Concepts(概念)。
四、实例
通过上面的分析,基本明白了这个抽象的数据结构封装的方式,其实就是在STL库的上层再封装一层,同时要保证多线程访问时的安全性,看下面的例子:
 1、首先对原TaskQueue进行改造:
#ifndef __TASKBUCKET_H__
#define __TASKBUCKET_H__#include "common.h"
#include <iostream>
#include <mutex>
#include <queue>template <typename T, typename Con = std::queue<T>> class TaskBucket final : public NoCopy {
public:TaskBucket() {}~TaskBucket() {}public:void Pop() { this->queue_.pop(); }void Push(T t) {std::cout << "Push bool:" << HasMemFuncCpp11<Con>::value << std::endl;std::unique_lock lock(this->mu_);if constexpr (HasMemFuncCpp11<Con>::value) {this->queue_.emplace_back(t);}}T Front() {std::unique_lock lock(this->mu_);return this->queue_.front();}T Back() {std::unique_lock lock(this->mu_);return this->queue_.back();}auto PopFront() {std::cout << "PopFront bool:" << HasMemFunc<Con>::value << std::endl;std::unique_lock lock(this->mu_);if constexpr (HasMemFunc<Con>::value) {T t = this->queue_.front();this->queue_.pop_front();return t;}}T PopBack() {std::cout << "PopBack bool:" << has_member_pop_back<Con>::value << std::endl;if constexpr (hasmem_pop_back<Con>::value) {std::unique_lock lock(this->mu_);T t = this->queue_.back();this->queue_.pop_back();return t;}}private:std::mutex mu_;Con queue_;
};
上面的代码主动对三部分进行了处理,一部分是增加了类本身不允许拷贝;另外一个增加了对容器的再次抽象,即容器可以动态传入;第三部分就是增加了对容器的成员函数通过SFINAE进行了判断。这也是模板技术在实际场景中应用的一种方式,下面会对其进行分析。需要注意的是,这里使用的c++17以上的编译环境。下面看一下相关的代码:
#ifndef __COMMON_H__
#define __COMMON_H__#include <functional>
#include <utility>
using CallBackMsg = std::function<void(int *, int)>;
using Task = std::function<void(int)>;class NoCopy {
protected:NoCopy() = default;~NoCopy() = default;public:NoCopy(const NoCopy &) = delete;NoCopy &operator=(const NoCopy &) = delete;NoCopy(NoCopy &&) = delete;NoCopy &operator=(NoCopy &&) = delete;
};
//第一种方式
template <typename T> struct HasMemFunc {
private:template <typename U> static auto Check(int) -> decltype(std::declval<U>().pop_front(), std::true_type());template <typename U> static std::false_type Check(...);public:enum { value = std::is_same<decltype(Check<T>(0)), std::true_type>::value };
};//Muduo库的使用方式(和上面类似):
template <typename T> struct hasPopFront {template <typename C> static char Check(decltype(&C::pop_front));template <typename C> static int32_t Check(...);const static bool value = sizeof(Check<T>(0)) == 1;
};//第二种方式
template <typename T> using Type = void;//处理无参
template <typename T, typename V = void> struct HasPop : std::false_type {};
template <typename T> struct HasPop<T, Type<decltype(std::declval<T>().pop_front())>> : std::true_type {};
//第二种方式-c++11后
template <typename T, typename V = void> struct HasMemFuncCpp11 : std::false_type {};template <typename T>
struct HasMemFuncCpp11<T, Type<decltype(std::declval<T>().emplace_back(std::declval<typename T::value_type>()))>>: std::true_type {};//第三种方式
#define HAS_MEM(mem)                                                                                             \template <typename T, typename... Args> struct hasmem_##mem {                                                 \private:                                                                                                             \template <typename U>                                                                                              \static auto Check(int) -> decltype(std::declval<U>().mem(std::declval<Args>()...), std::true_type());           \template <typename U> static std::false_type Check(...);                                                           \\public:                                                                                                              \enum { value = std::is_same<decltype(Check<T>(0)), std::true_type>::value };                                       \};
HAS_MEM(pop)
HAS_MEM(pop_back)
HAS_MEM(push)
#endif // __COMMON_H__
在上面代码中主要分成了三种形式,第一种和第二种是使用检查函数是否存在的普通方式,虽然看上去是使用了模板,但对成员函数名称还是进行了限制。所以如果需要限制多种成员函数还得手动写多个类;第三种使用宏的方式自动产生相关的模板代码,这样会少写不少的重复的类似的代码。
 这三种SFINAE的原理基本是一致的,虽然实现的手段有细节的不同。但都是通过decltype和std::declval(二者的区别可以查看前面的相关文章),通过是否能够符合模板自动匹配(即能否正常产生相关特化模板)来返回true 或false的value来达到判断是否存在函数的目的,然后通过if constexpr在编译期进行处理。Muduo库的使用方式前面分析过(“SFINAE的技巧应用”),基本原理也是如此,只是直接使用定义的函数匹配来得到值而非使用std::true_type这种形式,但逻辑是一样的。
 至于是否使用 std::is_same_v来替代 std::is_same,就看个人的兴趣了。
 大家分析的时候儿可以从第一种开始,简单的按分析说明一对就明白了,然后再到第二种,特别是Type类型能否获取,是value值的关键。到第三种基本就是前面的抽象版,理解了前面的两种方式,第三种就非常简单了。
 此处的封装未必是要求把所有的容器都封装起来,但它可以适应所有容器,什么意思呢?开发者可以根据自己的情况来选取指定的一两种容器来使用。当然,在实际的应用场景中,可能更多的是直接使用容器。这里之所以封装起来,就是为了给出一个更搞抽象的思路和方法。这点对于自己写容器而不使用STL中的容器的情况下更有意义。
 随着GCC等编译器对c++20支持的逐渐普及化。估计明后年,可能就可以普遍的使用c++20编程了(ubuntu22默认的g++11对c++20已经支持了大部分),这样就可以使用Concept以及其它相关的新特性,开发工作会变得更简单一些。
五、总结
在分析完成数据结构的封装后,基本也就明白了在多线程中如何使用STL中的容器。但是,一般对线程安全的操作控制,都是使用锁。不管锁的粒度是大是小,对性能的影响一般来说都是比较大的。而多线程特别是线程池恰恰又都是在高性能的场景下应用,所以这时就需要开发者认真考虑加锁引起的性能损失。
 当然,在后面可以考虑在某些情况下使用无锁编程技术,让数据的处理更快捷。但无锁编程也不是万能的,它也是有其相对的应用场景。万流归宗,还是需要开发者对整体技术的把握和实际应用场景进行综合考虑。
相关文章:
一步一步写线程之八线程池的完善之二数据结构封装
一、数据容器 在前面分析过,不管是线程任务的封装还是同步数据队列的封装,都是需要一个数据结构的。一用来说,如果没有什么特殊的原因,开发者都是使用STL中数据结构。比如前面经常见到的std::queue,std::deque,std::vector,std::…...
 
go连接数据库(原生)
根据官网文档 Go Wiki: SQL Database Drivers - The Go Programming Language 可以看到go可以连接的关系型数据库  常用的关系型数据库基本上都支持,下面以mysql为例 下载mysql驱动 打开上面的mysql链接 GitHub - go-sql-driver/mysql: Go MySQL Driver i…...
 
【C语言】2048小游戏【附源码】
欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 一、游戏描述: 2048是一款数字益智类游戏,玩家需要使用键盘控制数字方块的移动,合并相同数字的方块,最终达到数字方块上出现“2048”的目标。 每次移动操作,所…...
 
部署项目遇到的各种问题总结
文章目录 前言一、后端问题 jar包运行出现错误宝塔面板使用jdk17二、数据库问题 版本问题三、前端问题 连不上后端总结 前言 在做完项目之后,为了让别人访问到自己的网站,就需要部署前端后端以及数据库,但是在部署的过程中出现了各种问题和困…...
 
JavaSE:抽象类和接口
目录 一、前言 二、抽象类 (一)抽象类概念 (二)使用抽象类的注意事项 (三)抽象类的作用 三、接口 (一)接口概念 (二)接口语法规则 (三&a…...
 
发票是扫码验真好,还是OCR后进行验真好?
随着科技的进步,电子发票的普及使得发票的验真方式也在不断演进。目前,我们常见的发票验真方式主要有两种:一种是扫描发票上的二维码进行验真,另一种是通过OCR(Optical Character Recognition,光学字符识别…...
【AIGC调研系列】AIGC+Jmeter实现接口自动化测试脚本生成
AIGC(人工智能生成内容)结合JMeter实现接口自动化测试脚本生成的方法,主要涉及到通过流量收集工具和AIGC技术获取用户操作接口数据,并利用这些数据生成自动化测试脚本的过程。这种方法可以有效提高软件测试的效率和质量[1]。JMete…...
前端|babel升级
问题 项目不支持可选链调用过多的 babel 插件 步骤 基础包 dependencies “react-scripts”: “5.0.1” devDependencies “customize-cra”: “^1.0.0”,“react-app-rewired”: “^2.2.1”, 框架包 dependencies “react”: “16.13.1”,“react-dom”: “16.13.1”, …...
 
【微服务】spring状态机模式使用详解
一、前言 在很多系统中,通常会涉及到某个业务需要进行各种状态的切换操作,例如在审批流程场景下,某个审批的向下流转需要依赖于上一个状态的结束,再比如电商购物场景中,一个订单的生命周期往往伴随着不同的状态&#…...
 
【算法刷题day14】Leetcode:144.二叉树的前序遍历、94.二叉树的中序遍历、145.二叉树的后序遍历
文章目录 二叉树递归遍历解题思路代码总结 二叉树的迭代遍历解题思路代码总结 二叉树的统一迭代法解题思路代码总结 草稿图网站 java的Deque 二叉树递归遍历 题目: 144.二叉树的前序遍历 94.二叉树的中序遍历 145.二叉树的后序遍历 解析:代码随想录解析…...
mysql闲谈
如何定位慢查询 1、测试环境压测时,有的接口非常慢,响应时间超过2秒以上。当时系统部署了运维的监控系统Skywalking,在展示报表中可以看到是哪儿个接口慢,可以看到SQL具体执行时间。 2、如果没有类似的监控系统,在Mysq…...
 
物联网学习1、什么是 MQTT?
MQTT(Message Queuing Telemetry Transport)是一种轻量级、基于发布-订阅模式的消息传输协议,适用于资源受限的设备和低带宽、高延迟或不稳定的网络环境。它在物联网应用中广受欢迎,能够实现传感器、执行器和其它设备之间的高效通…...
 
【机器学习】数据探索(Data Exploration)---数据质量和数据特征分析
一、引言 在机器学习项目中,数据探索是至关重要的一步。它不仅是模型构建的基础,还是确保模型性能稳定、预测准确的关键。数据探索的过程中,数据质量和数据特征分析占据了核心地位。数据质量直接关系到模型能否从数据中提取有效信息ÿ…...
软件测试(一)--简介+主流技能+分类+模型+流程
一、软件及测试简介 1、软件生产过程 需求产生–需求文档–设计效果图–产品开发–产品测试(测试产品与需求文档是否一致)–部署上线 2、什么是软件测试 使用技术手段验证软件是否满足使用需求。 技术包括:(使用网络技术测试安…...
 
技术引领,策略升级:腾讯云与你共探数字金融新篇章
引言 2024 年 3 月 27 日下午,在北京腾讯总部,一场关于大模型与数据要素时代数字金融发展的深入讨论火热进行中。【TVP 走进腾讯:大模型与数据要素时代的数字金融发展论坛】是在腾讯二十年发展历程和数字化实践的基础上,进一步探索…...
 
数据库-root密码丢失的重置方案(win11环境)
当在windows系统中安装的mysql由于操作不当,或者密码遗忘,今天测试了一下,可以用以下方法重置root的密码。 mysqlwindows环境root密码重置问题 在win10/11环境下mysql8密码遗忘后的重置密码方案。 停止mysql服务 查找windows中的mysql服务名称…...
免试生常问的一些问题汇总---专升本学习篇
1.你怎么理解你申请的专业? 答:软件工程室一门涉及软件开发、维护和管理的工程学科。它结合了计算机科学、工程学和管理科学的原理,皆在通过系统化、规范化的方法来开发高质量的软件系统。 1.技术和支持 :软件工程包括编程语言、算法、数据结构、软件设计模式、软件测试、…...
FPGA的就业前景
FPGA(Field-Programmable Gate Array)技术在数字电路设计和嵌入式系统开发方面具有广泛的应用,因此在FPGA领域有着较好的就业前景。 目前,FPGA在通信、计算机、消费电子、汽车、航空航天等行业中得到了广泛应用。随着新一代通信网…...
7.阻塞模式与非阻塞模式
1.阻塞模式 一个线程来处理多个连接显得力不从心 accept等待连接 是一个阻塞方法 read读取SocketChannel中的数据 是一个阻塞方法 /*** 服务端* param args* throws IOException*/public static void main(String[] args) throws IOException {//建立一个缓冲区ByteBuffer b…...
 
Unity类银河恶魔城学习记录11-10 p112 Items drop源代码
Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释,可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili ItemObject_Trigger.cs using System.Collections; using System.Collecti…...
 
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
 
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
 
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
 
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
 
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
 
MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
