简单线程池的实现

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

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

然后在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判断的方式提高了获取单例对象的效率。

最后还有一些其它的锁:

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

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

相关文章

cesium vue 绘制标记实体(撒点),监听鼠标左击事件

添加实体 const viewer new Cesium.Viewer(cesiumContainer, {})viewer.entities.add()查看实体 const viewer new Cesium.Viewer(cesiumContainer, {}) const billboard viewer.entities.add({...})viewer.zoomTo(billboard)删除实体 根据实体删除 if (billboard.value…

【热门话题】Yarn:新一代JavaScript包管理器的安装与使用

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 Yarn&#xff1a;新一代JavaScript包管理器的安装与使用引言一、Yarn的安装1. 系…

MFC(一)搭建空项目

安装MFC支持库 创建空白桌面程序 项目相关设置 复制以下代码 // mfc.h #pragma once #include <afxwin.h>class MyApp : public CWinApp { public:virtual BOOL InitInstance(); };class MyFrame : public CFrameWnd { public:MyFrame();// 消息映射机制DECLARE_…

基于springboot+vue+Mysql的财务管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

【spring】AbstractApplicationContext 的refresh() 方法学习

上一篇我们一起学习了【spring】FileSystemXmlApplicationContext 类学习 AbstractApplicationContext 的refresh() 方法介绍 AbstractApplicationContext的refresh()方法仍然是整个Spring应用程序上下文初始化的核心流程入口。大体上的刷新生命周期依然保持一致。 refresh(…

【IEEE ACCESS】论文发表记录

官网&#xff1a;IEEE Access&#xff1a;多学科开放获取期刊 期刊水平&#xff1a; IEEE Access - 中国科学院文献情报中心期刊分区表升级版 (fenqubiao.com) 时间轴&#xff1a; 第一次提交&#xff1a;2024 年 1 月 17 日 第一次结果&#xff1a;2024 年 2 月 27 日 拒…

为什么网页提示SSL证书不信任?

随着传统行业迅速融入互联网&#xff0c;信息安全问题逐渐凸显&#xff0c;企业商业机密和国家信息安全面临网络威胁。为应对此挑战&#xff0c;国内企业已积极部署SSL证书&#xff0c;强化信息安全防护&#xff0c;确保业务安全稳定运行。 在企业部署SSL证书的过程中&#xf…

网络服务练习题

综合练习&#xff1a;请给 openlab 搭建 web 网站 网站需求&#xff1a; 1. 基于域名 www.openlab.com 可以访问网站内容为 welcome to openlab!!! 2. 给该公司创建三个子界面分别显示学生信息&#xff0c;教学资料 和缴费网站&#xff0c;基于&#xff0c; www.openlab.c…

面向对象:继承

文章目录 一、什么叫继承&#xff1f;二、单继承三、多继承3.1多继承的各种情况3.1.1一般情况3.1.1特殊情况&#xff08;菱形继承&#xff09; 四、菱形继承引发的问题4.1 问题1:数据冗余4.2 问题2:二义性&#xff08;无法确定到底是访问哪个&#xff09; 五、虚拟继承解决菱形…

Java毕业设计-基于springboot开发的原创歌曲分享平台-毕业论文+答辩PPT(附源代码+演示视频)

文章目录 前言一、毕设成果演示&#xff08;源代码在文末&#xff09;二、毕设摘要展示1、开发说明2、需求分析3、系统功能结构 三、系统实现展示1、平台功能模块2、后台功能模块2.1管理员功能模块2.2用户功能模块 四、毕设内容和源代码获取总结 Java毕业设计-基于springboot开…

威联通安装Kafka

最近在学习 Kafka 的知识&#xff0c;遇到一些问题网上搜到的信息不全。想要在本地安装一个 Kafka 进行验证&#xff0c;想到了之前买的 Nas 就开始折腾。 用 Docker 的方式安装 Kafka 现在的 Nas 很多都支持 Docker&#xff0c;我买的也支持。威联通的 Docker 叫 Container S…

基于vue的MOBA类游戏攻略分享平台的设计与实现|Springboot+Vue+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目持续更新中..... 2024年计算机毕业论文&#xff08;设计&#xff09;学生选题参考合集推荐收藏&#xff08;包含Springboot、jsp、ssmvue等技术项目合集&#xff09; 目录 1. …

Leo赠书活动-22期 【大模型在金融行业的应用场景和落地路径】文末送书

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠书活动专栏 ✨特色专栏&#xff1a;…

Go的数据结构与实现【Stack】

介绍 栈是存放值的一种特殊容器&#xff0c;在插入与删除值时&#xff0c;这种结构遵循后进先出&#xff08;Last-in-first-out&#xff0c;LIFO&#xff09;的原则&#xff0c;也就是说&#xff0c;值可以任意插入栈中&#xff0c;但每次取出的都是此前插入的最后一个值。 实…

Linux:详解TCP协议段格式

文章目录 认识TCPTCP协议段格式 本篇主要总结的是TCP协议的一些字段 认识TCP TCP协议全称是传输控制协议&#xff0c;也就是说是要对于数据的传输进行一个控制 以上所示的是对于TCP协议进行数据传输的一个理解过程 全双工 至此就可以对于TCP协议是全双工的来进行理解了&…

纸上得来终觉浅->代码详解锁升级

背景 最近再看关于锁升级的内容&#xff0c;一方面这个是编写代码时程序性能提高的一个利器&#xff0c;另一方面这部分也会是面试时候的热门话题。那么作者最开始也是通过b站视频包括一些csdn上面的资料去看&#xff0c;最终发现只是有一些结论&#xff0c;而没有具体的例子。…

​python学习之变量类型​

print单纯输中的十种数据类型只需要用print()函数即可&#xff0c;()里面直接写变量名。 下面重点介绍print格式输出&#xff1a; 第一种方法&#xff1a;一个萝卜一个坑&#xff0c;下面的代码中&#xff0c;{0}、{1}、{2}分别表示j,i,j*i&#xff0c;单引号里面是输出格式。…

什么是土壤墒情检测站?它在农业生产中有什么作用?

土壤墒情检测站是一种专门用于监测土壤水分状况和土壤水力性质的设备。它由多个传感器和数据采集单元组成&#xff0c;能够实时监测土壤中的水分含量、土壤温度等参数&#xff0c;并收集和记录相关的数据&#xff0c;提供土壤墒情&#xff08;即土壤水分状态&#xff09;的详细…

【学习】软件测试人员如何设计出优秀的测试用例

在软件开发的过程中&#xff0c;测试用例如同工程质量的守护者&#xff0c;它们的存在确保了软件产品的稳定性和可靠性。然而&#xff0c;如何设计出优秀的测试用例&#xff0c;让其在千变万化的软件世界中独领风骚&#xff0c;成为众多测试工程师追寻的目标。本文将为你揭示其…

Android RecyclerView 滑动后选中的条目居中显示

话不多说先看效果: 实录效果视频如下 滚动居中 RecyclerView 在原有的RecyclerView 基础上操作&#xff0c;其他步骤不变&#xff0c;只是替换一下 manager 步骤 导入依赖 maven { url https://www.jitpack.io }//无限滚动implementation com.github.ZhaoChanghu:GalleryLayou…