Linux 线程池

news/2024/12/31 3:44:20/

目录

    • 传统艺能😎
    • 概念😍
    • 线程池应用场景😘
    • 实现🤣
  • 静态方法的执行例程🤣
    • 设计任务类型😁
    • 主线程设计😂

传统艺能😎

小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦
在这里插入图片描述
1319365055

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,满怀希望,所向披靡,打码一路向北
一个人的单打独斗不如一群人的砥砺前行
这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我


在这里插入图片描述

概念😍

线程池是一种线程使用模式

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

线程池的优点

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

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

线程池应用场景😘

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

就像 web 服务器网页请求这样的任务,使用线程池技术是非常合适的:因为单个任务小,但是任务量巨大,你可以想象一个热门网站的点击量。

对于长时间任务,比如 Telnet 连接请求,线程池的优点就不明显了。因为 Telnet 会话时间比线程的创建时间大多了。突发性大量客户请求,在没有线程池的情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,但短时间内产生大量线程可能使内存到达极限,出现错误。

实现🤣

这里实现一个简单的线程池,线程池中提供了一个任务队列,以及若干个线程(多线程)
在这里插入图片描述
线程池中多个线程在任务队列中拿取线程任务,并执行,而对外就提供一个 Push 接口来从外面获取任务,将任务放进任务队列,代码如下:

#pragma once#include <iostream>
#include <unistd.h>
#include <queue>
#include <pthread.h>#define NUM 5//线程池
template<class T>
class ThreadPool
{
private:bool IsEmpty(){return _task_queue.size() == 0;}void LockQueue(){pthread_mutex_lock(&_mutex);}void UnLockQueue(){pthread_mutex_unlock(&_mutex);}void Wait(){pthread_cond_wait(&_cond, &_mutex);}void WakeUp(){pthread_cond_signal(&_cond);}
public:ThreadPool(int num = NUM): _thread_num(num){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_cond, nullptr);}~ThreadPool(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_cond);}//线程池中线程的执行例程static void* Routine(void* arg){pthread_detach(pthread_self());ThreadPool* self = (ThreadPool*)arg;//不断从任务队列获取任务进行处理while (true){self->LockQueue();while (self->IsEmpty()){self->Wait();}T task;self->Pop(task);self->UnLockQueue();task.Run(); //处理任务}}void ThreadPoolInit(){pthread_t tid;for (int i = 0; i < _thread_num; i++){pthread_create(&tid, nullptr, Routine, this); //注意参数传入this指针}}//往任务队列放任务(主线程调用)void Push(const T& task){LockQueue();_task_queue.push(task);UnLockQueue();WakeUp();}//从任务队列获取任务(线程池中的线程调用)void Pop(T& task){task = _task_queue.front();_task_queue.pop();}
private:std::queue<T> _task_queue; //任务队列int _thread_num; //线程池中线程的数量pthread_mutex_t _mutex;pthread_cond_t _cond;
};

为什么线程池中需要有互斥锁和条件变量? \color{red} {为什么线程池中需要有互斥锁和条件变量?} 为什么线程池中需要有互斥锁和条件变量?

线程池中的任务队列是被多个执行流同时访问的临界资源,因此我们需要引入互斥锁对任务队列进行保护。

线程池当中的线程要从任务队列里拿任务,前提条件是任务队列中必须要有任务,因此线程池当中的线程在拿任务之前,需要先判断任务队列当中是否有任务,若此时任务队列为空,那么该线程应该进行等待,直到任务队列中有任务时再将其唤醒,因此需要引入条件变量,当外部 Push 一个任务后,此时可能线程处于等待状态,因此就需要唤醒在条件变量下等待的线程。
6

注意:

当某线程被唤醒时,其可能是被异常或是伪唤醒,或是一些广播类的唤醒操作导致所有线程被唤醒,使得这些线程中只有个别线程能拿到任务。此时应该再次判断是否满足被唤醒条件,所以在判断任务队列是否为空时,应该使用 while 进行判断,而不是 if

pthread_cond_broadcast 函数的作用是唤醒条件变量下的所有线程,而外部可能只 Push 了一个任务,我们却把全部等待的线程唤醒,此时这些线程就都会去任务队列获取任务,但最终只有一个线程能得到任务。一瞬间唤醒大量的线程可能会导致系统振荡,这叫惊群效应。因此在唤醒线程时最好使用 pthread_cond_signal 函数唤醒正在等待的一个线程即可。

当线程从任务队列中拿到任务后,该任务就已经属于当前线程了,与其他线程已经没有关系了,因此应该在解锁之后再进行任务处理,某一线程拿到任务后,其他线程还需要等待该线程将任务处理完,才可以进入临界区。此时虽然是线程池,但最终我们可能并没有让多线程并行的执行起来。

静态方法的执行例程🤣

你可能注意到了线程池里面的执行例程全是静态方法,这时为什么呢?

Routine 是处理方法,作为类的成员函数,第一个参数是隐藏的 this 指针,虽然这里的 Routine 函数看起来只有一个参数,而实际上它有两个参数,此时直接将该Routine函数作为创建线程时的执行例程是不行的,无法通过编译。

静态成员函数属于类,而不属于某个对象,也就是说静态成员函数是没有隐藏的 this 指针的,因此我们需要将 Routine 设为静态方法,此时才真正只有一个参数,类型为 void*

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

设计任务类型😁

我们将线程池进行模板化,因此线程池中存储的任务类型可以是任意类型,但该任务类都必须包含一个 Run 方法,当处理该类型任务时只需调用该 Run 方法即可。

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

#pragma once
#include <iostream>//任务类
class Task
{
public:Task(int x = 0, int y = 0, char op = 0): _x(x), _y(y), _op(op){}~Task(){}//处理任务的方法void Run(){int result = 0;switch (_op){case '+':result = _x + _y;break;case '-':result = _x - _y;break;case '*':result = _x * _y;break;case '/':if (_y == 0){std::cerr << "Error: div zero!" << std::endl;return;}else{result = _x / _y;}break;case '%':if (_y == 0){std::cerr << "除零错误!" << std::endl;return;}else{result = _x % _y;}break;default:std::cerr << "错误操作!" << std::endl;return;}std::cout << "thread[" << pthread_self() << "]:" << _x << _op << _y << "=" << result << std::endl;}
private:int _x;int _y;char _op;
};

此时线程池不断从任务队列拿出任务进行处理,而并不需要关心这些任务是哪来的,它们只需要对任务执行对应 Run方法即可。

主线程设计😂

主线程就负责不断向任务队列 Push 任务就行了,此后线程池会从中获取到这些任务并进行处理:

#include "Task.hpp"
#include "ThreadPool.hpp"int main()
{srand((unsigned int)time(nullptr));ThreadPool<Task>* tp = new ThreadPool<Task>; //线程池tp->ThreadPoolInit(); //初始化线程池当中的线程const char* op = "+-*/%";//不断往任务队列塞计算任务while (true){sleep(1);int x = rand() % 100;int y = rand() % 100;int index = rand() % 5;Task task(x, y, op[index]);tp->Push(task);}return 0;
}

运行代码后一瞬间就有六个线程,有一个是主线程,另外五个在线程池内处理任务:

在这里插入图片描述

并且我们发现这五个线程在处理时有一定的顺序性,因为主线程每秒 Push 一个任务,这五个线程只会有一个线程获取到该任务,其他线程都会在等待队列中进行等待,当该线程处理完任务后就会因为任务队列为空而排到等待队列的最后,当主线程再次 Push 一个任务后会唤醒等待在首部的线程,如此循环,因此会表现出一定的顺序性!


http://www.ppmy.cn/news/190285.html

相关文章

华为Mate20 HL1HIMAM电路图

华为Mate20 HL1HIMAM手机电路原理图纸 品牌&#xff1a;华为 型号&#xff1a;Mate20 版号 HL1HIMAM 图纸内容&#xff1a;电路图 图纸格式PDF 高清PDF&#xff0c;共56页&#xff0c;可放大缩小&#xff0c;复制搜索 华为Mate20手机图纸–电路原理图

android p矢量壁纸,华为Mate20系列14张高清壁纸“极致AI!”

华为将于今年10月16日在伦敦举行发布会&#xff0c;发布Mate 20系列新品手机。根据此前曝光的官方渲染图&#xff0c;华为Mate20 Pro将采用带刘海的异形曲面全面屏设计&#xff0c;并支持3D结构光技术&#xff0c;同时背面采用“浴霸”造型的三摄像头。现在&#xff0c;关于华为…

华为Mate 20电脑模式说明

华为年度旗舰机Mate 20于10月16日在伦敦发布&#xff0c;Mate 20作为华为超级旗舰&#xff0c;除了自身携带的麒麟980处理器、4200万像素徕卡三摄等顶配&#xff0c;Mate 20还具备双向无线充电和电脑模式功能&#xff0c;可谓亮点十足&#xff0c;下面绿豆就为大家简单介绍下华…

华为Mate20RS HL2LAYAM原理图纸

华为Mate20RS HL2LAYAM原理图纸 品牌 华为 型号 Mate20RS 版号 HL2LAYAM 图纸类型 手机图纸 图纸内容 手机电路图 图纸格式 PDF 共60页

华为手机如何连接无线打印服务器,惊呆了!华为Mate 20居然支持无线打印

原标题&#xff1a;惊呆了&#xff01;华为Mate 20居然支持无线打印 在办公室里&#xff0c;想要打印一些资料、照片&#xff0c;日常情况基本是这样的&#xff1a;如果文件在电脑里&#xff0c;那么只要选择局域网里的打印机就可以了&#xff0c;但如果文件在手机里&#xff0…

我的测试之路:从入坑测试到月薪15k...

“干过保险卖过房&#xff0c;做过销售做过网管”这是我毕业后前两年的真实写照&#xff0c;因为所学网络安全专业不好找工作&#xff0c;毕业之后为了生活只能将就的干着这种门槛低的工作。后来一次同学聚会被同学带下坑后&#xff0c;正式转行软件测试。 刚入坑的两年&#…

深度学习(卷积神经网络)

文章目录 动物视觉神经&#xff0c;以及脑科学视网膜——视觉第一站外膝体——信息中转站视皮层——中央处理器小tips 人工神经网络神经认知机模型卷积神经网络结构&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;卷积层池化层全连接层输出层softmax函数…

Linux-系统物理CPU个数、CPU核数

首先要明确物理cpu个数、核数、逻辑cpu数的概念 1.物理cpu数&#xff1a;主板上实际插入的cpu数量&#xff0c;可以数不重复的 physical id 有几个&#xff08;physical id&#xff09; 2.cpu核数&#xff1a;单块CPU上面能处理数据的芯片组的数量&#xff0c;如双核、四核等 …