[Linux]线程池

[Linux]线程池

文章目录

  • [Linux]线程池
    • 线程池的概念
    • 线程池的优点
    • 线程池的应用场景
    • 线程池的实现

线程池的概念

线程池是一种线程使用模式。线程池是一种特殊的生产消费模型,用户作为生产者,线程池作为消费者和缓冲区。

线程过多会带来调度开销,进而影响缓存局部和整体性能,而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。

线程池的优点

  • 线程池避免了在处理短时间任务时创建与销毁线程的代价。
  • 线程池不仅能够保证内核充分利用,还能防止过分调度。

注意: 线程池中可用线程的数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

线程池的应用场景

  1. 需要大量的线程来完成任务,且完成任务的时间比较短。
  2. 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  3. 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。

线程池的实现

下面我们实现一个简单的线程池,线程池中提供了一个任务队列,以及若干个线程(多线程)。

image-20231029145843142

  • 线程池中的多个线程负责从任务队列当中拿任务,并将拿到的任务进行处理。
  • 线程池对外提供一个Push接口,用于让外部线程能够将任务Push到任务队列当中。

线程池的代码如下:

#pragma once#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <vector>
#include <queue>const int N = 5; // 线程池内线程数量template <class T>
class ThreadPool
{
public:ThreadPool(int num = N) : _num(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}void LockQueue(){pthread_mutex_lock(&_mutex);}void UnLockQueue(){pthread_mutex_unlock(&_mutex);}void threadWait(){pthread_cond_wait(&_cond, &_mutex);}void threadWakeUP(){pthread_cond_signal(&_cond);}T getTask(){T t = _tasks.front();_tasks.pop();return t;}bool isEmpty(){return _tasks.empty();}static void *threadRoutine(void *args){pthread_detach(pthread_self());ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);while (true){tp->LockQueue();while (tp->isEmpty()){tp->threadWait();}T t = tp->getTask();tp->UnLockQueue();t.Run();//任务处理}}void Start(){pthread_t tid;for (int i = 0; i < _num; i++){pthread_create(&tid, nullptr, threadRoutine, this);}}void PushTask(T &task) // 添加任务{LockQueue();_tasks.push(task);threadWakeUP();UnLockQueue();}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}private:int _num;                        // 线程数std::queue<T> _tasks;            // 任务队列pthread_mutex_t _mutex; // 保证互斥访问任务队列这一共享资源pthread_cond_t _cond;   // 根据任务队列中的任务数量控制线程的等待和运行
};

为什么线程池中需要有互斥锁和条件变量?

互斥锁: 任务队列是一个共享资源,外部线程可以调用添加任务的接口访问任务队列,线程池内部的线程可以直接访问任务队列处理任务,可能会造成任务队列的并发访问问题,因此需要利用互斥锁保护任务队列中的数据。

条件变量: 线程池当中的线程要从任务队列里拿任务,前提条件是任务队列中必须要有任务,因此线程池当中的线程在拿任务之前,需要先判断任务队列当中是否有任务,若此时任务队列为空,那么该线程应该进行等待,直到任务队列中有任务时再将其唤醒,因此我们需要引入条件变量。

当外部线程向任务队列中Push一个任务后,此时可能有线程正处于等待状态,因此在新增任务后需要唤醒在条件变量下等待的线程。

为什么线程池中的线程执行例程需要设置为静态方法?

使用pthread_create函数创建线程时,需要为创建的线程传入一个执行方法threadRoutine,该执行方法只有一个参数类型为void的参数,以及返回类型为void的返回值。

如果threadRoutine作为类的成员函数,该函数的第一个参数是隐藏的this指针,无法通过编译。而静态成员函数属于类,而不属于某个对象,也就是说静态成员函数是没有隐藏的this指针的,因此我们需要将threadRoutine设置为静态方法,此时threadRoutine函数才真正只有一个参数类型为void的参数。

但是在静态成员函数内部无法调用非静态成员函数,而我们需要在threadRoutine函数当中调用该类的某些非静态成员函数。因此我们需要在创建线程时,向threadRoutine函数传入的当前对象的this指针,此时我们就能够通过该this指针在threadRoutine函数内部调用非静态成员函数了。

任务类型的设计

由于线程池编写的是模板化的,因此任务类型可以是任意的,但是由于处理任务的逻辑是通过调用任务的Run函数,因此任务类中必须实现Run函数才能使用该线程池。

例如,实现一个计算任务类如下:

#include <cstdlib>
#include <iostream>class Task
{
public:Task(int x, int y, char op) : _x(x), _y(y), _op(op), _result(0), _exitcode(0){}void Run()//对传入数据进行操作{switch (_op){case '+':_result = _x + _y;break;case '-':_result = _x - _y;break;case '*':_result = _x * _y;break;case '/':if (_y == 0) _exitcode = -1;else_result = _x / _y;break;case '%':if (_y == 0) _exitcode = -2;else_result = _x % _y;break;default:break;}std::string result = std::to_string(_x) + _op +  std::to_string(_y) + "=" + std::to_string(_result) + "(exicode:" + std::to_string(_exitcode);std::cout << result << std::endl;}private:int _x;//左操作数int _y;//右操作数char _op;//操作符int _result;//算数结果int _exitcode;//退出码
};

线程池内的线程在从任务队列拿出任务进行处理的过程,并不需要关心这些任务的类型和来源,只需要拿到任务后执行对应的Run方法即可。

主线程实现

主线程只需要不断向任务队列当中Push任务就行了,此后线程池当中的线程会从任务队列当中获取到这些任务并进行处理。

#include "ThreadPoolv1.hpp"
#include "Task.hpp"
#include <memory>
#include <ctime>using namespace std;int main()
{std::unique_ptr<ThreadPool<Task>> tp(new ThreadPool<Task>());tp->Start();time(nullptr);const char* ops = "+-*/%";while(true){int x, y;x = rand() % 50;y = rand() % 50;char op = ops[rand()%5];Task t(x, y, op);tp->PushTask(t);sleep(1);}return 0;
}

运行代码后会产生六个线程,其中一个是主线程,另外五个是线程池内处理任务的线程:

image-20231029152724549

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

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

相关文章

在docker中创建EMQX 加数据卷

1、从虚拟容器中复制出来文件 docker run --rm emqx/emqx:5.3.0 sh -c cd /opt/emqx && tar -c etc | tar -C $PWD -x 2、将这三个文件夹分别赋予最高权限&#xff0c;也可以777可以755 chmod -R 777 data chmod -R 777 etc chmod -R 777 log 3、创建容器代码 docke…

加解密原理(HCIA)

一、加密技术 1、加密的两个核心组件 2、加密技术作用&#xff1a; 二、加解密技术原理 1、对称加密 2、非对称加密 &#xff08;1&#xff09;思考问题&#xff1f; 1&#xff09;、有了非对称加密为什么还用对称加密&#xff1f; 2&#xff09;、如何传递秘钥呢&…

服务器动态/静态/住宅/原生IP都是什么意思

​  在互联网的世界中&#xff0c;我们经常会听到关于IP地址的各种说法&#xff0c;比如服务器动态IP、静态IP、住宅IP和原生IP。那么这些术语究竟代表着什么意思呢?让我们一起来了解一下。 动态IP 动态IP(Dynamic IP)是指互联网服务提供商(ISP)在每次用户上网时&#xff0c…

Redis中的数据类型以及适用场景

1.Redis中的数据类型 Redis中的数据类型包括&#xff1a;String(字符串&#xff09;、Hash(字典)、List(列表)、Set(集合)、Sorted Set【Zset】(有序集合&#xff09;。 Redis 所有的数据结构都是一个key对应一个value&#xff0c;不同类型的数据结构之间的差异就在于value的…

【微信小程序开发】学习小程序的网络请求和数据处理

前言 网络请求是微信小程序中获取数据和与服务器交互的重要方式。微信小程序提供了自己的API来处理网络请求&#xff0c;使得开发者可以轻松地在微信小程序中实现数据的获取和提交。本文将介绍微信小程序中的网络请求&#xff0c;包括使用wx.request发起GET和POST请求&#xf…

jenkins实践篇(1)——基于分支的自动发布

问题背景 想起初来公司时&#xff0c;我们还是在发布机上直接执行发布脚本来运行和部署服务&#xff0c;并且正式环境和测试环境的脚本都在一起&#xff0c;直接手动操作脚本时存在比较大的风险就是将环境部署错误&#xff0c;并且当时脚本部署逻辑还没有检测机制&#xff0c;…

JVM面试知识点整理

文章目录 (一) JVM组成JVM组成部分和运行流程从图中可以看出 JVM 的主要组成部分运行流程&#xff1a;程序计数器Java堆虚拟机栈方法区堆栈的区别是什么&#xff1f; (二) 类加载器双亲委派模型类装载的执行过程 (三) 垃圾回收对象什么时候可以被垃圾回收哪些可以作为根对象 垃…

【QT】Qt控件不显示图标

问题描述 本人在跟着B站视频学习QT时&#xff0c;遇到了一件十分悲惨的事情&#xff0c;一模一样的步骤&#xff0c;我的图标却不能显示。 于是我上网查询一下解决方案&#xff0c;第一种&#xff0c;亲测没用&#xff1b;第二种亲测可以。 解决方法一 1、构建 -> 清理项目…

ArcGIS Maps SDK for JS:隐藏地图边框

文章目录 1 问题描述2 解决方案 1 问题描述 近期&#xff0c;将ArcGIS Api for JS v4.16更新到了ArcGIS Maps SDK for JS v4.27&#xff0c;原本去除地图的css代码失效了。 v4.26及以前版本 &#xff0c;需要用.esri-view-surface--inset-outline:focus::after 控制边框属性。…

假如我有一台服务器,我会让它提供三种服务

一、提供照片上传、存储和下载服务 随着移动互联网时代的持续快速发展&#xff0c;PC互联网日益势微&#xff0c;各大互联网门户网站的博客、空间也跟着凋零&#xff0c; 作为博客、空间的标配功能的相册也随之被关闭。 2019年3月6日网易相册发布停运公告并于当年5月8日正式停…

基于RK3568高性价比全国产EMS储能解决方案(二)设计方案

目录 版 本 修 订 记 录 1. 产品介绍 1.1. 什么是XM3568-EP 1.2. 产品特点 1.3. 外壳尺寸 1.4. 外壳外观 1.5. 规格参数 2. 设备使用介绍 2.1. 下载需要使用到的驱动和调试工具 2.2. 启动网关 2.3. DEBUG串口的使用方法 2.4. LED指示灯说明 3. Linux系…

0029Java程序设计-家政服务平台管理系统

文章目录 摘要目录系统设计开发环境 摘要 本文首先研究并介绍国内外目前的背景和现状&#xff0c;在此基础上给出论文的主要研究内容&#xff0c;其次&#xff0c;对家政服务平台管理系统的需求进行了分析。再次&#xff0c;对家政服务平台管理系统进行了总体设计&#xff0c;…

【java学习—九】模板方法(TemplateMethod)设计模式(4)

文章目录 1. 在java中什么是模板2. 模板方法设计解决了什么问题&#xff1f;3. 代码化理解 1. 在java中什么是模板 抽象类体现的就是一种模板模式的设计&#xff0c;抽象类作为多个子类的通用模板&#xff0c;子类在抽象类的基础上进行扩展、改造&#xff0c;但子类总体上会保留…

Jest和Mocha对比:两者之间有哪些区别?

什么是单元测试&#xff1f; 所谓单元测试&#xff0c;是对软件中单个功能组件进行测试的一种软件测试方式&#xff0c;其目的是确保代码中的每一个基本单元都能正常运行。因此&#xff0c;开发人员在应用程序开发的整个过程&#xff08;即代码编写过程&#xff09;中都需要进行…

单例模式及其使用场景

单例模式&#xff08;Singleton&#xff09;&#xff1a;指在一个系统中某个类只存在一个实例&#xff0c;类中自行实例化&#xff0c;实例向该系统提供统一的访问接口。 单例模式有两种表现形式&#xff0c;饿汉式&#xff1a;类加载时&#xff0c;就进行实例化&#xff1b;懒…

【目标检测】Visdrone数据集和CARPK数据集预处理

之前的博文【目标检测】YOLOv5跑通VisDrone数据集对Visdrone数据集简介过&#xff0c;这里不作复述&#xff0c;本文主要对Visdrone数据集和CARPK数据集进行目标提取和过滤。 需求描述 本文需要将Visdrone数据集中有关车和人的数据集进行提取和合并&#xff0c;车标记为类别0&…

在 Windows 用 Chrome System Settings 设置代理

在 Windows 用 Chrome System Settings 设置代理 贴心提示&#xff1a;在设置代理之前&#xff0c;请确保您已经安装了 浏览器。 &#x1f527; 设置代理的详细步骤如下&#xff1a; 打开 浏览器&#xff0c;输入 //settings/system 并回车。 在「系统和网络设置」页面中&am…

vue源码分析(四)——vue 挂载($mount)的详细过程

文章目录 前言一、使用RuntimeCompiler解析$mount的原因二、$mount 解析的详细过程1.解析挂载的#app执行了vm.$mount2. 通过$mount方法执行以下文件的mount方法3. 执行util工具文件夹中的query方法4. 执行query方法后返回$mount方法判断el是否是body5. 判断!options.render&…

Android原生项目集成uniMPSDK(Uniapp)遇到的报错总结

uni小程s序SDK 集成到Android原生项目:老项目中用到的库较多&#xff0c;会出现几种冲突问题&#xff0c;总结如下&#xff1a; 报错1&#xff1a; Execution failed for task :app:processDebugManifest. > Manifest merger failed with multiple errors, see logs Andro…