个人主页:企鹅不叫的博客
专栏
- C语言初阶和进阶
- C项目
- Leetcode刷题
- 初阶数据结构与算法
- C++初阶和进阶
- 《深入理解计算机操作系统》
- 《高质量C/C++编程》
- Linux
⭐️ 博主码云gitee链接:代码仓库地址
⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!
【Linux】第一章环境搭建和配置
【Linux】第二章常见指令和权限理解
【Linux】第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)
【Linux】第四章 进程(冯诺依曼体系+操作系统+进程概念+PID和PPID+fork+运行状态和描述+进程优先级)
【Linux】第五章 环境变量(概念补充+作用+命令+main三个参数+environ+getenv())
【Linux】第六章 进程地址空间(程序在内存中存储+虚拟地址+页表+mm_struct+写实拷贝+解释fork返回值)
【Linux】第七章 进程控制(进程创建+进程终止+进程等待+进程替换+min_shell)
【Linux】第八章 基础IO(open+write+read+文件描述符+重定向+缓冲区+文件系统管理+软硬链接)
【Linux】第九章 动态库和静态库(生成原理+生成和使用+动态链接)
【Linux】第十章 进程间通信(管道+system V共享内存)
【Linux】第十一章 进程信号(概念+产生信号+阻塞信号+捕捉信号)
【Linux】第十二章 多线程(线程概念+线程控制)
【Linux】第十三章 多线程(线程互斥+线程安全和可重入+死锁+线程同步)
【Linux】第十四章 多线程(生产者消费者模型+POSIX信号量)
线程池: 一种线程使用模式。线程过多会带来调度开销,而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。
- 线程池避免了在处理短时间任务时创建与销毁线程的代价。
- 线程池不仅能够保证内核充分利用,还能防止过分调度。
- 需要大量的线程来完成任务,且完成任务的时间比较短。可同时处理多任务,多请求。
- 有任务可以立即从线程池中调取线程取处理,节省了线程创建的时间,提高性能
- 有效防止服务端线程过多而导致系统过载的问题
- 线程池占用资源少,但是健壮性不强
- 进程池占用资源多,但是健壮性更强(涉及进程间通信,可利用管道的阻塞队列实现进程间同步和互斥)
- 一个队列: 存放任务
- 线程池中线程数: 记录线程池中创建的线程数
- 互斥量: 互斥锁
- 条件变量: 队列为空时的条件变量
框架:
#pragma once #include
#include #include #include #include #include #include #include using namespace std; #define NUM 5 template <class T> class ThreadPool { public: ThreadPool(int threadNum = NUM) :threadNum_(threadNum) ,isStart_(false) { pthread_mutex_init(&mutex_, nullptr); pthread_cond_init(&cond_, nullptr); } ~ThreadPool() { pthread_mutex_destroy(&mutex_); pthread_cond_destroy(&cond_); } //加锁 void lockQueue() { pthread_mutex_lock(&mutex_); } //解锁 void unlockQueue() { pthread_mutex_unlock(&mutex_); } //判空 bool IsEmpty() { return taskQueue_.empty(); } //等待 void Wait() { pthread_cond_wait(&cond_, &mutex_); } //唤醒 void WakeUp() { pthread_cond_signal(&cond_); } private: bool isStart_; //线程池是否开始 int threadNum_; //线程池中线程的数量 queue<T> taskQueue_; //任务队列 pthread_mutex_t mutex_; pthread_cond_t cond_; };
- 线程池中,创建多个线程需要设置静态方法:
原本threadRoutine函数参数只有一个void*类型
创建线程的时候,把
this
指针传过去,让启动函数的arg
参数去接收即可作为成员函数,第一个参数默认是this指针,编译无法通过。静态成员函数属于类,但不属于某个对象,静态成员函数没有this指针,所以将threadRoutine设置成静态函数,只有一个参数void*。
静态成员函数内部无法调用非静态成员函数,创建线程的时候,把
this
指针传过去,让启动函数的args
参数去接收即可
- 线程池中互斥锁和条件变量作用:
线程池中的任务队列是会被多个执行流同时访问的临界资源,因此我们需要引入互斥锁对任务队列进行保护。
线程池中的线程拿任务的时候判断线程池中是否有任务,如果队列此时没有任务,则阻塞等待,直到有任务被唤醒,需要引入条件变量
当外部线程向任务队列中Push一个任务后,需要唤醒等待的线程。
- 使用while:
防止某个线程出现异常导致影响其他线程
//成员函数,都有默认参数this,而static函数没有this指针 static void *threadRoutine(void *args) { pthread_detach(pthread_self()); ThreadPool<T> *tp = (ThreadPool<T> *)args; while (1) { tp->lockQueue(); while (tp->IsEmpty()) { tp->Wait(); } //拿任务 T t = tp->pop(); tp->unlockQueue(); // 解锁后处理任务 int x, y; char oper; t.get(&x, &y, &oper); cout << "新线程完成计算任务: " << x << oper << y << "=" << t.run() << endl; } } void start() { assert(!isStart_); for (int i = 0; i < threadNum_; i++) { pthread_t temp; pthread_create(&temp, nullptr, threadRoutine, this);//注意参数传入this指针 } isStart_ = true; }
放任务: 主线程往任务队列中放任务,放任务之前进行加锁,放完任务解锁,然后唤醒在条件变量下等待的队列
取任务: 线程池中的线程从任务队列中取任务,这里不需要加锁,因为这个动作在启动函数中加锁的那一段区间中被调用的,其实已经上锁了//往任务队列塞任务(主线程调用) void push(const T &task) { lockQueue(); taskQueue_.push(task); WakeUp(); unlockQueue(); } //从任务队列获取任务(线程池中的线程调用) T pop() { T temp = taskQueue_.front(); taskQueue_.pop(); return temp; }
实现一个任务类,生产者把这个任务放进阻塞队列中,消费者取出并进行处理。其中还有一个run的任务执行方法
#pragma once #include
#include using namespace std; class Task { public: Task() : _x(0), _y(0), _op('?') { } Task(int x, int y, char op) : _x(x), _y(y), _op(op) { } ~Task() { } int operator()() { return run(); } int 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) { cout << "div zero, abort" << endl; result = -1; } else { result = _x / _y; } } break; case '%': { if (_y == 0) { cout << "mod zero, abort" << endl; result = -1; } else { result = _x % _y; } } break; default: cout << "非法操作: " << _op << endl; break; } return result; } int get(int *e1, int *e2, char *op) { *e1 = _x; *e2 = _y; *op = _op; } private: int _x; int _y; char _op; };
主线程负责创建线程池,然后放任务到线程池即可
#include "threadpool.hpp" #include "test.hpp" #include
#include using namespace std; const string operators = "+/*/%"; int main() { srand((unsigned int)time(nullptr)); ThreadPool<Task> *tp = new ThreadPool<Task>; tp->start(); // 派发任务的线程 while (true) { int x = rand() % 50; int y = rand() % 10; char oper = operators[rand() % operators.size()]; cout << "主线程派发计算任务: " << x << oper << y << "=?" << endl; Task t(x, y, oper); tp->push(t); sleep(1); } } 结果:主线程是每秒Push一个任务,线程池就处理一个任务
[Jungle@VM-20-8-centos:~/lesson36/threadpool]$ ./main 主线程派发计算任务: 40%3=? 新线程完成计算任务: 40%3=1 主线程派发计算任务: 25+4=? 新线程完成计算任务: 25+4=29 主线程派发计算任务: 38/4=? 新线程完成计算任务: 38/4=9 主线程派发计算任务: 30%8=? 新线程完成计算任务: 30%8=6
注意: 如果想让线程池处理其他不同的任务请求时,只需要提供一个任务类,在该任务类当中提供对应的任务处理方法就行了。