Linux下线程池(ThreadPool)
目录
一.线程池相关概念及其优点
二.线程池的实现
一.线程池相关概念及其优点
线程池一种线程使用模式,线程过多会带来这个调度的开销进而影响整体的性能,而线程池是提前准备好了线程等待着管理者进行分配任务。
1.线程池的优点主要有一下几个方面:
- 线程重用: 线程的创建和销毁的开销是巨大的,而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么多消费内存的开销,其线程执行速度也是突飞猛进的提升。
- 控制线程的并发数:线程池,控制线程池的并发数可以有效的避免大量的线程池争夺CPU资源而造成堵塞。
- 可以对线程进行管理: 线程池可以提供定时、定期、单线程、并发数控制等功能。比如通过ScheduledThreadPool线程池来执行S秒后,每隔N秒执行一次的任务。
2.线程池的应用场景:
- 需要大量的线程来完成任务,并且完成任务所需的时间比较短
- 对性能要求苛刻的应用,比如说要求服务器迅速响应客户端的请求
- 接受突发性的大量请求但不至于让服务器因此产生大量线程。
二.线程池的实现
再这里实现的是一个简单的线程池,再这个线程池当中有一个任务队列,线程从任务队列当中提前任务,已经若干个线程,一开始再特点的条件变量下进行等待。任务队列是典型的生产者-消费者模型,本模型至少需要两个工具:一个 mutex + 一个条件变量,或是一个 mutex + 一个信号量。mutex 实际上就是锁,保证任务的添加和移除(获取)的互斥性,一个条件变量是保证获取 task 的同步性:一个 empty 的队列,线程应该等待(阻塞)。
- 线程池当中多个线程负责从任务队列当中拿任务,并处理拿到的任务
- 线程池对外提供Push方法让外部将任务放入到任务队列当中,好让线程从任务队列当中拿到数据
对应线程池的实现
#pragma once #include "Task.hpp" #include <iostream> #include <pthread.h> #include <queue> #define NUM 5 template <class T> class ThreadPool { private:void LockQueue(){pthread_mutex_lock(&_mtx);}void UnlockQueue(){pthread_mutex_unlock(&_mtx);}void Wait(){pthread_cond_wait(&_cond, &_mtx);}void WakeUp(){pthread_cond_signal(&_cond);}bool IsEmpty(){return _task_queue.size() == 0;}public:static void *Routine(void *arg)//为什么必须是static方法在博客中细说{auto *self = (ThreadPool<T> *)arg;pthread_detach(pthread_self());//分离线程while (true){self->LockQueue();while (self->IsEmpty())//看此时的任务队列当中是否有任务{self->Wait();}T task;self->pop(&task);self->UnlockQueue();task.Run();//处理任务}}void Push(const T &val){LockQueue();_task_queue.push(val);UnlockQueue();WakeUp();//唤醒在条件变量下等待的一个线程}void pop(T *out){*out = _task_queue.front();_task_queue.pop();}void InitThreadPool(){//初始化线程池并创建线程pthread_t tid;for (int i = 0; i < _num; i++){pthread_create(&tid, NULL, Routine, this);}}//销毁信号量~ThreadPool(){pthread_mutex_destroy(&_mtx);pthread_cond_destroy(&_cond);}//获取单例static ThreadPool<T> *GetInstance(){static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;if (NULL == tp){pthread_mutex_lock(&mtx);//多个线程可能同时进来if (NULL == tp){tp = new ThreadPool<T>();}pthread_mutex_unlock(&mtx);}return tp;}private:ThreadPool(int num = NUM) : _num(num){pthread_mutex_init(&_mtx, NULL);pthread_cond_init(&_cond, NULL);}//禁掉拷贝构造和赋值ThreadPool(const ThreadPool<T>&tp)=delete;ThreadPool&operator=(const ThreadPool<T>&tp)=delete;static ThreadPool<T> *tp;pthread_mutex_t _mtx;pthread_cond_t _cond;int _num;//线程的数量std::queue<T> _task_queue;//任务队列 }; template <class T> ThreadPool<T> *ThreadPool<T>::tp = NULL;
代码解释:
在这里需要条件变量和互斥锁的原因是:多个线程可能同时访问任务队列,此时这个任务队列就变成了临界资源,锁以我们需要引入互斥锁来保护临界资源。线程池中的线程去这个任务队列当中拿任务的前提必须是任务队列当中要有任务,如果此时我们只使用互斥锁那么就有可能出现互斥锁一直被一个线程占用着,这是不合理的因此我们需要引入条件变量。当外部向任务队列当中放入任务之后,需要唤醒在条件变量下等待的线程。在这里特别需要注意的有一下几点:
- 在判断任务队列是否为空的情况下我们不能使用if进行判断。因为线程此时可能是被伪唤醒而此时任务队列是空的又或者是一些广播唤醒比如pthread_cond_broadcast把所有的线程都唤醒了。此时可能就会出现问题,所以我们需要使用while进行判断而不是if
- 当线程从任务队列当中拿到任务之后这个任务就属于这个线程了,所以任务的处理过程不应该放在临界区里面,而应该放在临界区的外部。处理任务是需要时间的,
- 在唤醒线程时建议使用pthread_cond_signal而不是pthread_cond_broadcast,后者会将在该条件变量下等待的所有线程全部唤醒,但是这些线程去任务队列当中拿任务但是只有一个线程能够拿到任务,一瞬间唤醒全部的线程可能会导致系统震荡也叫做惊群问题。
可能信心的老铁会发现为什么线程执行的方法是静态的?
我们在学习C++的时候我们知道非静态的成员函数默认会传递this指针,并且是第一个参数这就和我们线程库的参数形式冲突了所以我们需要将其设置为静态的函数。但是这也引发了另外一个问题线程在执行Routine函数时无法调用其他的成员函数,静态的成员函数是属于整个类的而不是属于某个对象,所以了我们需要将this作为参数显示的传进去这样我们就可以在这个静态的成员函数类调用其他函数了
任务类
在这里我们线程池处理的任务,我们将其封装成一个类,这个类提供一个Run方法用于执行任务在这里为了简单只是简单的设置成为一个计算任务类:
#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 << "Error: mod zero!" << std::endl;return;}else{result = _x % _y;}break;default:std::cerr << "operation error!" << std::endl;return;}std::cout << "thread[" << pthread_self() << "]:" << _x << _op << _y << "=" << result << std::endl;} private:int _x;int _y;char _op; };
此时线程池中的线程只需要从任务队列中拿到任务然后执行Run方法即可.下面我们来看一下主线程的执行逻辑:主线程主要负责通过线程池提供的Push方法将任务放入到任务队列当中即可
#include<iostream> #include<unistd.h> #include"ThreadPool.hpp" int main() {ThreadPool<Task>*tp=ThreadPool<Task>::GetInstance();tp->InitThreadPool();const char*op="+-*/%";while(true){sleep(1);int x=rand()%100;int y=rand()%100;int index=rand()%5;Task t(x,y,op[index]);tp->Push(t);}return 0; }
我们发现代码运行的一瞬间就出现了6个线程,其中一个是主线程另外5个是线程池里面的5个线程。到此线程池就结束了,如果线程池需要处理其他的任务时我们只需要更改任务类,并且在这个任务类当中提供一个Run方法即可。
Linux下线程池(ThreadPool)相关推荐
- linux下线程池实现
linux下线程池实现 转自:http://blog.csdn.net/lmh12506/article/details/7753952 前段时间在github上开了个库,准备实现自己的线程池的,因为 ...
- 简单实现Linux下线程池
最近在Linux下使用mysql时有时会报查询异常,看网上解决方案是多次并发使用,通过gdb调试也找到问题,主要是上次查询结果集未释放,最终导致如此. 大佬说根本解决方案还是线程池,就去看了线程池的一 ...
- linux 下线程池
基本想法是这样的: 1.预创建的线程通过mutex休眠在线程池中.这样,通过unlock该mutex就可以唤醒该线程了: 2.出于简单性的目标,一个线程池内的所有线程的属性都是 ...
- Linux下线程池源码实现
线程池: 初始化阶段创建有最大数量限制的线程,以及一个线程安全的任务队列,若有任务需要处理,则将任务抛入线程池中,线程池中的的线程就会去处理这个任务. 优点 1.避免峰值压力下,资源耗尽的风险: 2. ...
- 谈一谈linux下线程池
什么是线程池: 首先,顾名思义,就是把一堆开辟好的线程放在一个池子里统一管理,就是一个线程池. 其次,为什么要用线程池,难道来一个请求给它申请一个线程,请求处理完了释放线程不行么?也行,但是如果创建线 ...
- Linux下线程池概念详解以及代码演示
线程池 概念 多个线程的组合,但是总数量不会超出池子的限制.是多执行流并发/并行处理,每当任务到来都会从池子里取出一个空闲的线程去进行处理. 产生原因 线程若是不限制数量的创建,在峰值的压力下,线程创 ...
- C++ Linux下线程池的实现
线程池提供设计图 转载于:https://www.cnblogs.com/zhanggaofeng/p/9338601.html
- Linux 创建线程 段错误,linux下线程池,出现Segmentation fault (core dumped) 问题
GDB调试段错误,可以有效快速定位到出错该行. 1.让系统在信号中断造成的错误时产生core文件修改core文件大小,需要su权限:#查看core文件设置 ulimit -a#设置core大小为无限 ...
- C#.Net使用线程池(ThreadPool)与专用线程(Thread)
线程池(ThreadPool)使用起来很简单,但它有一些限制: 1. 线程池中所有线程都是后台线程,如果进程的所有前台线程都结束了,所有的后台线程就会停止.不能把入池的线程改为前台线 程. 2. 不能 ...
最新文章
- AIX HA模拟宕机--维护磁带机
- golang中的select详解
- Ubuntu 下 Git 服务器的安装和初级配置
- AppDelegate瘦身之服务化
- jquery-jquery对向与dom标签对向
- 自动化运维工具Ansible实战(四)常用模块
- 当开发人员遇上非功能性需求
- IIS6.0服务器架站无法访问解决方案总结
- (转)SQL Server当中生成一定范围的随机数
- 微信小程序——实现时钟样式
- C# 重绘tabControl,添加关闭按钮(页签)
- android界面设计中用的字体,APP界面设计必备!最全UI设计字体规范
- 优酷 DSP 广告投放系统架构实践
- 联想thinkpad e430 设置u盘启动
- Linux-服务器管理操作
- win7虚拟机_虚拟机VMware 15安装教程
- FastCGI 进程管理器(FPM)
- Scheduler (Long-term,Short-term, Medium-term Scheduler) Dispatcher
- idea安装阿里巴巴java规范插件的两种方式
- C++ split() 函数