一步一步写线程之八线程池的完善之二数据结构封装
一、数据容器
在前面分析过,不管是线程任务的封装还是同步数据队列的封装,都是需要一个数据结构的。一用来说,如果没有什么特殊的原因,开发者都是使用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…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
基于matlab策略迭代和值迭代法的动态规划
经典的基于策略迭代和值迭代法的动态规划matlab代码,实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...