简单线程池的实现

线程池的代码可以写的很复杂,这里就稍微简单一些

首先来看一下线程池的原则,下面的大框是服务器,而在服务器中维护一个任务队列。

然后在server中预先创建一批线程,这批线程和任务队列合在一起只用向外界提供一个入队列的接口。

未来如果任务队列中有任务,这批线程就去执行任务,如果没有任务这批线程就去阻塞。

这个模式不就是一个生产消费模型吗?

只不过这里没有提供生产者,而所有的线程都是消费者从一个共同的任务队列中拿取任务。原理就是这样的。

然后对于线程池还需要一个小组件,就是之前我写过的一个很简单的日志系统。

对于这个日志系统详细的实现,如若不介意,请看我的下面这篇文章:

完成后就可以将这个小组件放到线程池中了。对于线程池的总体思路上面已经说明过了,但是在这里再复习一下。在完成了基本的日志函数之后,在日志的实现函数中增加一些东西

这样都是便于我们去使用日志。

然后下面就是线程池的大体逻辑,创建一批线程,这批线程发现先任务队列中不为空就拿出任务然后去执行,如果任务队列为空就阻塞等待。

然后为了更好的创建线程,将我们之前封装好的线程拿过来使用,同时也就需要将lockguard一起拿过来使用了。

以下就是需要的文件:

其中的Main.cc中写的就是测试代码。

下面就来写线程池的类。

既然是一个类就需要有成员变量,那么线程池中要有什么呢?首先就是需要一个储存任务的空间,这里使用一个队列来储存任务(当然也可以将之前写的阻塞队列拿过来,这里就不那么使用了),然后就是线程了。因为存在多个线程,为了管理这些线程所以需要使用一个vector的数组来储存这些线程,同时因为我写的这个thread类中需要一个结构体用于当作线程的数据。所以还需要一个类作为ThreadData。

以下就是一个Threadpool类的大体框架了:

然后需要来完成线程池的任务了,第一个任务就是要将线程池启动起来,所以这提供一个start函数,用于启动线程池。然后线程池中既然存在一个任务队列,自然也要提供一个函数用于向任务队列中输入任务。如果线程池启动起来了,自然要有线程的存在才行,这里有两种写法,第一种:将对应数量线程的创建写到构造函数中。第二种:写到start函数中。这里我选择写到了构造函数中。

Thread的构造函数:

threadpool的构造

下面就是线程池的启动start函数了。既然线程池启动起来了。自然就是让所有的线程启动起来了。因为我封装的Thread中是由start这个方法的,所以这里可以这么写。如果使用的是c++11线程库中的线程则不能怎么写。

然后我们就来完成构建一个线程所需要的事物(线程需要执行的任务,线程的名字)。

对于线程需要执行的任务,这里可以直接写一个静态/类外的方法,使用静态方法的好处就是能够访问类内的成员,并且没有this指针。但是这里也可以写一个不是静态的方法,因为在Thread中回调函数是一个包装器,那么我们只需要传递一个可调用对象即可()。

这个可调用对象的返回值为void,参数为T&。

所以这里就可以这么写:

这个函数内部是包含了一个this指针的,所以需要做一些小的处理。使用一个bind绑定这个threadRun这个任务,做了这个处理之后,也就意味着,线程只会执行线程池内部给线程的任务了。

这样也就将类中的方法绑定给了线程去执行。

其中的​​std::bind(&threadpool<T>::threadRun, this, std::placeholders::_1)​​:这部分使用了 ​​std::bind​​ 来创建一个可调用对象。解释其中的部分:

  • ​&threadpool<T>::threadRun​​:这是一个成员函数指针,指向 threadRun 函数,该函数似乎是属于 threadpool 类的成员函数。
  • ​this​​:在这个上下文中,this 指向当前对象,即 threadpool 类的一个实例。
  • ​std::placeholders::_1​​:这是一个占位符,表示在调用可调用对象时将会提供的参数

除此之外bind还有一个作用就是将ThreadRun这个函数的this指针去除:

​std::bind​​ 的作用是将成员函数 ​​threadRun​​ 绑定到特定的对象上,同时去除 ​​this​​ 指针,以便在后续调用时作为一个普通函数对象使用。这样做的目的是为了使得 ​​threadRun​​ 能够符合 ​​Thread​​ 构造函数所需的函数类型,即返回类型为 ​​void​​,参数为 ​​T&​

现在为了证明线程一旦启动起来就会去执行ThreadRun函数。这里做一个简单的实验。

然后为了让这个实验能够运行下去,在threadpool类中在增加一个wait方法。让主线程能够join等待线程池中的线程。

运行截图:

现在已经基本能够运行起来了,下面就是要完成我们的线程需要完成的具体的任务了。也就是我们的线程需要从任务队列中拿取任务,然后去执行任务了,因为这个任务队列是能够被多个线程共享的,是临界资源,所以需要上锁。这里先使用原生的上锁函数,最后在修改为LockGuard

下面继续来写ThreadRun函数:

然后使用LockGuard优化一下上锁的过程

这里还可以将线程等待函数做一下简单的封装。

到这里为止我们都还没有使用过日志,对于日志在完成了大体的函数之后,再增加即可。

然后线程池还需要提供一个函数,这个函数的功能就是往任务队列中push任务。

在这个函数中完成往任务队列中push任务,同时要去唤醒在等待的线程。

然后就是push函数了

但是现在的问题就是没有任务啊,这里就要将之前写的那个Task类拿过来继续使用了(实现了一个简单的+-乘除取模的任务)。

然后就可以让主线程去构建一些简单的任务了。

运行截图:

现在就能完成对应任务的派生和处理了。

然后不是还有一个ThreadData的类还没有写吗?下面就来写一下这个类。为了方便打印日志,需要知道当前执行的这个线程的名字是什么,所以这里就暂时只写一个name作为成员了,这样的话在线程执行对应的任务的时候也能拿到名字。

然后就能打印日志了。也就是将log使用上来。

同时在修改一下push函数:

这里使用emplace_back()对任务进行插入,这里的底层就是使用了移动拷贝,减少对象的拷贝次数。然后在push这里再打印一个信息。

然后启动的时候再打印一下日志

然后就是push任务的时候再打印一下日志:

然后因为这里将休眠函数做了封装,所以这里也能完成再休眠函数和唤醒函数中写日志。

不能让唤醒函数去打印唤醒信息,因为唤醒函数是主线程做的,这里主线程不能拿到子线程的name。

然后不要忘了如果这里的日志是打印在显示器上的,而显示器也是一个临界资源也是需要加锁去访问的。

运行结果:

但是这样式有极大的概率出现打印问题的,原因之前也已经说明过了,显示器也是一个临界资源。这里我不想再去给显示器加锁了,所以我让这些日志信息打印到文件中去。

调用log中的Eable函数修改一下显示方式即可。

此时所有的日志信息就已经写到文件中了。

而这个线程池其实本质也就是一个生产消费模型。只不过线程池的任务,并不是由创建出来的线程生产的,而是由主线程生产的。而当后面学习了网络之后,就可以通过网络去获取任务了。

对于这个线程池还存在可以扩展的部分:

在线程池中会存在两种数据,第一个是线程的个数,第二个数任务的个数。

这里可以在增加两个变量第一个变量是线程数量的低水位线,一个是线程数量的高水线(就是两个整型变量)。对于这个下限和上限的具体的数量,是根据业务情况而定的,这里就可以写一个配置文件,在配置文件中写明低水位线为多少,高水位线为多少,然后在构造函数中读取这个文件,将低水位线和高水位线的值获取到即可。

还有一种数据也就是任务的数量,依旧是有着任务的低水位线,和任务的高水位线。导入的方法也是一样的。

有了这些之后,在一个线程获取任务之前,可以进行一次自我检查。

如果当前的任务个数已经超过了高水位线,但是线程数量还没有到达高水位线,此时这个线程就是创建更多的线程。

创建的逻辑很简单就是往线程数组中push,然后启动这个线程就可以了(不要忘了增加_thread_num)。

下一种情况,如果任务的个数本来就不多(小于了任务低水位线),而且线程的个数已经大于等于高水位线(或者是高水位线的一半等等),此时任务少线程多,此时就让这个线程直接退出即可(更新线程的个数)。完成这个任务 之后这个线程池就是一个浮动的线程池了。

然后拓展2:就是可以将这个线程池和之前写的进程池结合起来变成一个多进程版本的多线程池。

最后还有一些额外的点:

stl容器在多线程的使用中都是线程不安全的。

但是虽然智能指针是线程安全的,但是智能指针指向的对象不一定是线程安全的。

然后还有一个单例模式(饿汉和懒汉模式),下面先将上面的线程池改成一个单例模式的线程池(懒汉模式【使用时创建对象】)

在单例模式中构造函数是需要的,但是需要设定为私有的,而拷贝构造/赋值直接设定为不生成。

增加单例指针

最后增加一个获取单例的函数

但是这样写的单例是存在问题的,这个问题之后会解决这里先去测试一下这个单例是否正确。这里再在GetInstance处增加一个日志信息,显示单例被创建。

运行一下:

这里因为线程调度的原因导致了这个信息在后面才被打印,单例的创建一定是在之前的。

但是这里的Getinstance也是存在线程安全问题的,这里获取单例这个函数只有被main函数调用,那么在未来这个获取单例的函数会不会被多个线程一起调用呢?当然是可能的。所以这里还需要增加一把锁,这里我选择了一把静态锁。

然后将这个锁使用到Getinstance函数中:

这样写有几个好处,当有好几个线程检测到这里的_instance为nullptr时,会都进入到第一个if中,然后在这里获取锁,而只有一个线程能够得到锁,然后去到第二个if中创建单例对象,之后这个线程会释放锁,其它线程获取到锁之后,此时_instance已经不是nullptr了自然就不会继续创建单例对象了。

这里通过双if判断的方式提高了获取单例对象的效率。

最后还有一些其它的锁:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【php程序开发从入门到精通】——搭建PHP开发环境

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

搜索与图论——Floyd算法求最短路

floyd算法用来求多源汇最短路 用邻接矩阵来存所有的边 时间复杂度O(n^3) #include<iostream> #include<cstring> #include<algorithm>using namespace std;const int N 20010,INF 1e9;int n,m,k; int g[N][N];void floyd(){for(int k 1;k < n;k ){f…

计算机网络(第八版)-第1章课后习题参考答案

计算机网络(第八版)-第1章课后习题参考答案 本文是对自己之前文章的格式化&#xff1a;https://blog.csdn.net/qq_46396470/article/details/132788972?spm1001.2014.3001.5502 T1-01 计算机网络向用户可以提供哪些服务&#xff1f; 连通性和共享 &#xff0c;例如音频&…

docker环境配置过程中的常见问题

1、pull镜像问题 docker pull jenkins/jenkins:lts Using default tag: latest Trying to pull repository docker.io/library/centos ... Get https://registry-1.docker.io/v2/library/centos/manifests/latest: Get https://auth.docker.io/token?scoperepository%3Alibr…

基于Spring Boot 3 + Spring Security6 + JWT + Redis实现接口资源鉴权

紧接上一篇文章&#xff0c;基于Spring Boot 3 Spring Security6 JWT Redis实现接口资源鉴权 系列文章指路&#x1f449; 系列文章-基于SpringBoot3创建项目并配置常用的工具和一些常用的类 项目源码&#x1f449; /shijizhe/boot-test 文章目录 1. 修改 UserDetailsServic…

(学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

在ubuntu上搭建系统监控系统

大纲 数据生产方安装和运行验证 数据收集、存储和分发方下载和解压修改配置运行验证 数据消费方下载和运行验证新增数据源新增看板关联看板和数据源效果展现 参考资料 在一个监控系统中&#xff0c;一定会有“数据生产方”和“数据消费方”存在。“数据生产方”用于产出需要监控…

三个表的联合查询的场景分析-场景4:c表维护a和b表的id关联关系(一对多)

基础SQL演练&#xff0c;带详细分析&#xff0c;笔记和备忘。 目录 背景介绍 表数据 需求1&#xff1a;查询g表所有记录&#xff0c;以及关联的h的id 需求2&#xff1a;在需求1基础上&#xff0c;查出关联的h的其它字段&#xff08;name&#xff09; 需求3&#xff1a;在需…

Java基本语法(变量,数据类型,关键字、)

目录 什么是变量 声明 声明方式 赋值方式 声明的同时进行赋值 变量命名规范 字符组成&#xff1a;变量名可以包含以下字符&#xff1a; 开头限制&#xff1a; 空格禁止&#xff1a; 关键字/保留字&#xff1a; 大小写敏感&#xff1a; 长度限制&#xff1a; 推荐风…

软考101-上午题-【信息安全】-网络安全

一、网络安全 1-1、安全协议 SSL(Secure Socket Layer&#xff0c;安全套接层)是 Netscape 于 1994年开发的传输层安全协议&#xff0c;用于实现 Web 安全通信。1996 年发布的 SSL3.0 协议草案已经成为一个事实上的Web 安全标准。 端口号是43。 SSL HTTP HTTPS TLS(Transpo…

FL Studio21中文版百度云网盘下载及切换中文语言教程

FL Studio 21&#xff0c;即广为人知的“水果”软件&#xff0c;拥有众多强大的功能&#xff0c;满足了音乐制作人在创作过程中的各种需求。 首先&#xff0c;它具备出色的多轨道音频录制功能&#xff0c;能够同时处理多个音频轨道的录制&#xff0c;非常适合制作复杂的音乐作…

K8s Pod亲和性、污点、容忍度、生命周期与健康探测详解(中)

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Kubernetes航线图&#xff1a;从船长到K8s掌舵者》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 在上一章节中&#xff0c;我们详细探讨了Pod的亲和性&…

基于 StarRocks 的风控实时特征探索和实践

背景 金融风控特征是在金融领域中用于评估和管理风险的关键指标。它们帮助金融机构识别潜在风险&#xff0c;降低损失&#xff0c;并采取措施规避风险。例如&#xff0c;用户最后一次授信提交时间就是一个重要的金融风控特征。 金融风控实时特征场景是一个典型的大数据实时业务…

代码随想录算法训练营 DAY 24 | 回溯理论基础 77.组合 + 剪枝优化

回溯理论 回溯法就是递归函数&#xff0c;纯暴力搜索 解决的问题 组合&#xff08;无顺序&#xff09; 1 2 3 4 给出大小为2的所有组合 切割字符串 子集问题 1 2 3 4&#xff0c;子集有1 2 3 4,12,13,14&#xff0c;…123 124… 排列&#xff08;有顺序&#xff09; 棋盘…

OpenAI发布Voice Engine模型!用AI合成你的声音!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

合集:JS异步的六个解决方案详解。

Hello&#xff0c;各位老铁&#xff0c;最近发表了js异步的解决方案&#xff0c;是分开发的&#xff0c;这次我把他汇总起来&#xff0c;方便大家收藏、查看&#xff0c;欢迎点赞评论私信交流。 01.详解&#xff1a;JS异步解决方案之回调函数&#xff0c;及其弊端 02.详解&…

函数指针的运用

这段代码使用了函数指针&#xff0c;实现了根据用户输入的命令选择不同的操作&#xff0c;并对两个数进行相应的处理。以下是代码的总结&#xff1a; getMax, getSmall 和 getSum 函数分别用于获取两个数中的较大值、较小值和它们的和。 dataHandler 函数接收两个数据 data 和…

ElementUI表格table组件实现单选及禁用默认选中效果

在使用ElementUI&#xff0c;需要ElementUI表格table组件实现单选及禁用默认选中效果, 先看下效果图&#xff1a; 代码如下&#xff1a; <template><el-tableref"multipleTable":data"tableData"tooltip-effect"dark"style"widt…

2024 ccfcsp认证打卡 2022 03 02 出行计划

import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt(); // 出行计划数目int m sc.nextInt(); // 查询个数int k sc.nextInt(); // 等待核酸检测结果所需时间final int N 200010;i…

ROS 2边学边练(4)-- 何为主题(topics)

概念 主题是一种节点间的通信方式&#xff0c;某个节点充当发布特定&#xff08;主题&#xff09;消息&#xff08;数据&#xff09;的角色&#xff0c;另外一些节点则可以订阅接收该特定&#xff08;主题&#xff09;消息&#xff08;数据&#xff09;。两者&#xff0…