🏆个人主页:企鹅不叫的博客
🌈专栏
⭐️ 博主码云gitee链接:代码仓库地址
⚡若有帮助可以【关注+点赞+收藏】,大家一起进步!
💙系列文章💙
【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】第十四章 多线程(生产者消费者模型+POSIX信号量)
💎一、线程池
🏆1.线程池概念
线程池: 一种线程使用模式。线程过多会带来调度开销,而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。
🏆2.线程池价值
- 线程池避免了在处理短时间任务时创建与销毁线程的代价。
- 线程池不仅能够保证内核充分利用,还能防止过分调度。
🏆3.线程池应用
- 需要大量的线程来完成任务,且完成任务的时间比较短。可同时处理多任务,多请求。
- 有任务可以立即从线程池中调取线程取处理,节省了线程创建的时间,提高性能
- 有效防止服务端线程过多而导致系统过载的问题
🏆4.线程池和进程池
- 线程池占用资源少,但是健壮性不强
- 进程池占用资源多,但是健壮性更强(涉及进程间通信,可利用管道的阻塞队列实现进程间同步和互斥)
🏆5.线程池实现
概述
- 一个队列: 存放任务
- 线程池中线程数: 记录线程池中创建的线程数
- 互斥量: 互斥锁
- 条件变量: 队列为空时的条件变量
框架:
#pragma once #include <iostream> #include <cassert> #include <queue> #include <memory> #include <cstdlib> #include <pthread.h> #include <unistd.h> #include <sys/prctl.h> 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参数并返回void的函数,非静态成员函数隐含this指针作为第一个参数,这样会有两个参数不符合要求。
为什么创建线程要将this指针传过去:
静态成员函数内部无法调用非静态成员函数,创建线程的时候,把this
指针传过去,线程函数可以访问线程对象的所有成员线程池中互斥锁和条件变量作用:
线程池中的任务队列是会被多个执行流同时访问的临界资源,因此我们需要引入互斥锁对任务队列进行保护。
线程池中的线程拿任务的时候判断线程池中是否有任务,如果队列此时没有任务,则阻塞等待,直到有任务被唤醒,需要引入条件变量
当外部线程向任务队列中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 <iostream> #include <string> 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 <ctime> #include <thread> 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
注意: 如果想让线程池处理其他不同的任务请求时,只需要提供一个任务类,在该任务类当中提供对应的任务处理方法就行了。