简单线程池的实现

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

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

然后在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.hqwc.cn/news/576786.html

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

相关文章

直流马达驱动芯片D6289ADA介绍

应用领域 适用于智能断路器&#xff08;家用或工业智能空开&#xff09;、新能源汽车充电枪锁、电动玩具、电磁门锁、自动阀门等的直流电机驱动。 功能介绍 D6289ADA是一款直流马达驱动芯片&#xff0c;它有两个逻辑输入端子用来控制电机前进、后退及制动。该电路具有良好的抗干…

[每日一氵] 将服务器的某个端口映射为另一台服务器的ssh连接地址

拓扑结构图&#xff0c;小火龙如何通过服务器A的某个端口ssh连接到服务器B呢? ssh连接准备 首先开启服务器B的ssh连接&#xff0c;若显示: ssh xxlocalhostssh: connect to host localhost port 22: Connection refused也许是ssh没安装 sudo apt-get install openssh-ser…

Linux应用实战之网络服务器(四)JavaScript介绍

0、前言 准备做一个Linux网络服务器应用实战&#xff0c;通过网页和运行在Linux下的服务器程序通信&#xff0c;这是第四篇&#xff0c;介绍一下JS&#xff0c;让HTML网页实现与服务器通信。 1、JS常用语法 JavaScript是一种脚本语言&#xff0c;主要用于前端开发&#xff0…

知识图谱与大数据:区别、联系与应用

目录 前言1 知识图谱1.1 定义1.2 特点1.3 应用 2 大数据2.1 定义2.2 应用 3. 区别与联系3.1 区别3.2 联系 结语 前言 在当今信息爆炸的时代&#xff0c;数据成为了我们生活和工作中不可或缺的资源。知识图谱和大数据是两个关键概念&#xff0c;它们在人工智能、数据科学和信息…

Vulnhub:MY FILE SERVER: 1

目录 信息收集 1、arp 2、nmap 3、whatweb WEB web信息收集 dirmap FTP匿名登录 enum4linux smbclient showmount FTP登录 ssh-kegen ssh登录 提权 系统信息收集 脏牛提权 get root 信息收集 1、arp ┌──(root㉿ru)-[~/kali/vulnhub] └─# arp-scan -l I…

Python中模块

基本概念 **模块 module&#xff1a;**一般情况下&#xff0c;是一个以.py为后缀的文件 ①Python内置的模块&#xff08;标准库&#xff09;&#xff1b; ②第三方模块&#xff1b; ③自定义模块。 包 package&#xff1a; 当一个文件夹下有 init .py时&#xff0c;意为该文…

Soot入门学习笔记

Soot 适合参考的文档和教程如下&#xff1a; 北京大学软件分析技术 南京大学软件分析 Tutorials for soot McGill University 198:515 (vt.edu) 比较好的笔记资料&#xff1a; 南京大学《软件分析》课程笔记 比较好的入门作业或者案例&#xff1a; CSCE710 Assignmen…

备忘录删除了怎么恢复?解锁4个简单方法

误删除苹果备忘录是一个常见的问题&#xff0c;而且很容易导致我们遗失重要信息的情况。但是&#xff0c;如果您不幸误删了备忘录&#xff0c;也不必过分担心&#xff0c;因为有几种简单的方法可以帮助您恢复这些备忘录。备忘录删除了怎么恢复&#xff1f;在本文中&#xff0c;…

编译与链接(想了解编译与链接,那么看这一篇就足够了!)

前言&#xff1a;在我们练习编程的时候&#xff0c;我们只需要将代码写入、运行&#xff0c;就可以得到计算之后的结果了&#xff0c;但是你有没有想过&#xff0c;为什么就可以得到计算之后的结果呢&#xff0c;它的底层又到底是什么呢&#xff1f; ✨✨✨这里是秋刀鱼不做梦的…

全面了解电子邮件的优点和不足之处

没有任何一种通信方式能像电子邮件一样长期如此受欢迎。当你想到忙碌的职业人士在企业或办公室环境中工作时&#xff0c;你可能会想象他们正专心致志地给某人写邮件&#xff0c;按照指示传递信息。电子邮件的优点和缺点是什么&#xff1f;优点包括易于访问、透明度高&#xff0…

DC-DC芯片D1509, 适用于工控主板、TV板卡、安卓主板、车载功放电源等产品方案应用。

一、应用领域 适用于工控主板、TV板卡、安卓主板、车载功放电源等产品方案应用。 二、功能介绍 D1509是芯谷科技推出的一款输入耐压40V、输出电压1.23-37V可调、输出电流最大2.0A的高效率、高精度DC-DC芯片&#xff0c;其输出电压有固定3.3V、5.0V和12.0V的版本&#xff0c;…

Android Studio控制台输出中文乱码问题

控制台乱码现象 安卓在调试阶段&#xff0c;需要查看app运行时的输出信息、出错提示信息。 乱码&#xff0c;会极大的阻碍开发者前进的信心&#xff0c;不能及时的根据提示信息定位问题&#xff0c;因此我们需要查看没有乱码的打印信息。 解决步骤&#xff1a; step1: 找到st…