linux通过c++实现线程池类
线程池的实现
线程池已基于C++11重写
: 基于C++11实现线程池的工作原理
前言
初学C++,想封装点常用的C++类,已经写好了mutex,cond,thread的类,想用起来写点东西,于是就决定写线程池了,这里拙笔记录下学习笔记.
本文主要内容包括: 线程池的概念
、使用原因
、适用场景
、线程池的实现
、任务调度逻辑
、样例测试
.
线程池的概念
线程池是指在一个多线程程序中创建一个线程集合,在执行新的任务的时候不是新建一个线程,而是使用线程池中已创建好的线程,一旦任务执行完毕,线程就会休眠等待新的任务分配下来,线程池中的线程数量取决于机器给进程所能分配的内存大小,以及应用程序的需求.
使用原因及适用场合
1.在服务器端编程中,最原始的方法我们使用顺序化的结构,一个服务器只能处理一个客户,如果同时2个客户端链接上来了,服务器只能先处理了先到达的那个个,这样第二个客户端只能等了,影响客户的响应时间.它只适用于客户量少的短连接.这时候有方案2.
2.在多线程服务器端编程中,一个服务器如果要处理多条链接的客户端,当链接很少的时候我们可以每来一条链接创建一个线程。但当并发量很大的时候呢,不停地的增加线程,在某个时间计算机资源可能耗尽.于是有了方案3
3.为了弥补方案2中每个请求创建线程的缺陷,我们使用固定大小线程池,全部IO交给IO复用线程解决(本文不涉及),而任务计算交给线程池.如果任务彼此独立,IO压力不大,那么这种方案非常适合.
当然服务器模型远不止这3种,还有很多方案,本文不涉.
线程池的实现原理
线程池类主要维系两个队列:任务队列
,线程队列
线程池通过take方法从线程队列提取任务,到一个线程中去执行; 有任务就提取执行,无任务则阻塞线程休眠.
任务队列
可以单独写个任务类出来,也可以写个任务类基类,预留虚任务函数接口,继承下来泛化.
当然最便利的方法就是直接用函数地址来做任务咯.
typedef void (*Task)(void);
线程队列
线程队列通过我自己写的线程类实现.
#include <pthread.h>
class Thread{
public:
typedef void (*threadFun_t)(void *arg);
explicit Thread(const threadFun_t &threadRoutine, void *arg);
~Thread();
void start();
void join();
static void *threadGuide(void *arg);
pthread_t getThreadId() const{
return m_threadId;
}
private:
pthread_t m_threadId;
bool m_isRuning;
threadFun_t m_threadRoutine;
void *m_threadArg;
};
Thread::Thread(const threadFun_t &threadRoutine, void *arg)
:m_isRuning(false),
m_threadId(0),
m_threadRoutine(threadRoutine),
m_threadArg(arg){
}
Thread::~Thread(){
if(m_isRuning){//如果线程正在执行,则分离此线程.
CHECK(!pthread_detach(m_threadId));
}
}
void *Thread::threadGuide(void *arg){
Thread *p = static_cast<Thread *>(arg);
p->m_threadRoutine(p->m_threadArg);
return NULL;
}
void Thread::join(){
VERIFY(m_isRuning);
CHECK(!pthread_join(m_threadId, NULL));
m_isRuning = false;
}
void Thread::start(){
pthread_attr_t attr;
CHECK(!pthread_attr_init(&attr));
//CHECK(!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)); //set thread separation state property
CHECK(!pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)); //Set thread inheritance
CHECK(!pthread_attr_setschedpolicy(&attr, SCHED_OTHER)); //set thread scheduling policy
CHECK(!pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM)); //Set thread scope
CHECK(!pthread_create(&m_threadId, &attr, threadGuide, this));
m_isRuning = true;
}{
MutexLockGuard lock(m_mutex);
m_isRuning = false;
}
}
构造传入带一个空类型参数指针(为什么要带这个空类型指针后面会提
)函数指针,通过start()方法创建线程,然后执行threadGuide()方法调用构造时候传入的函数指针执行咱们想运行的函数实现Thread类.
这两个队列在线程池中的定义如下:
private:
std::vector<Thread *> m_threads;
std::deque<Task> m_tasks;
任务调度逻辑
任务分配逻辑主要靠两个条件变量实现,(条件变量本文不做详述)
1.任务队列是否空.
2.任务队列是否满.
其逻辑如下图所示:
start()方法是线程池的运行方法.通过它创建线程池.
threadRoutine()就是我们就是线程池中创建的线程,
线程跑起来后,通过 isRunning 控制线程循环是否退出.
stop()方法关闭线程池,回收资源.
循环中判断 : 有任务则执行,无任务则wait 阻塞等待.
void ThreadPool::start(){
m_isRuning = true;
m_threads.reserve(m_threadsSize);
for(size_t i = 0; i < m_threadsSize; i++){
m_threads.push_back(new Thread(threadRoutine, this));
m_threads[i]->start();
}
}
Thread(threadRoutine, this) 这里就是为什么我线程类要带一个无符号类型指针参数的原因,因为静态函数无法调用c++的类成员函数(主要原因是类在编译期间未实例化没有明确的地址.)我们只能通过线程池对象的this指针调用它的成员.
任务调度的源码实现:
ThreadPool::ThreadPool(size_t tasksSize, size_t threadsSize)
:m_tasksSzie(tasksSize),
m_threadsSize(threadsSize),
m_mutex(),
m_tasksEmpty(m_mutex),
m_tasksFull(m_mutex),
m_isRuning(false){
}
ThreadPool::~ThreadPool(){
if(m_isRuning){
stop();
}
}
void ThreadPool::threadRoutine(void *arg){
ThreadPool *p = static_cast<ThreadPool *>(arg);
while(p->m_isRuning){
ThreadPool::Task task(p->take());
if(task){
task();
}
}
}
ThreadPool::Task ThreadPool::take(){
MutexLockGuard lock(m_mutex);
while(m_tasks.empty() && m_isRuning){
m_tasksEmpty.wait();
}
if(!m_tasks.empty()){
Task task = m_tasks.front();
m_tasks.pop_front();
m_tasksFull.notify();
return task;
}
return NULL;
}
void ThreadPool::addTask(Task task){
if(m_threads.empty()){//如果线程池是空的,直接跑任务.
task();
}
else{
MutexLockGuard lock(m_mutex);
while(m_tasksSzie > 0 && m_tasks.size() >= m_tasksSzie){
m_tasksFull.wait();
}
m_tasks.push_back(task);
m_tasksEmpty.notify();
}
}
void ThreadPool::start(){
m_isRuning = true;
m_threads.reserve(m_threadsSize);
for(size_t i = 0; i < m_threadsSize; i++){
m_threads.push_back(new Thread(threadRoutine, this));
m_threads[i]->start();
}
}
void ThreadPool::stop(){
{
MutexLockGuard lock(m_mutex);
m_isRuning = false;
m_tasksEmpty.notifyAll();
}
for(int i = m_threadsSize - 1; i >= 0; i--){
m_threads[i]->join();
delete(m_threads[i]);
m_threads.pop_back();
}
}
程序测试
测试代码:
创建一个有两个线程,拥有5个任务的任务队列,执行8个加数任务.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "MutexLock.hh"
#include "Thread.hh"
#include <unistd.h>
#include "Condition.hh"
#include "ThreadPool.hh"
#include <vector>
//threadPool test
MutexLock CntLock;
int cnt = 0;
void test(void){
unsigned long i = 0xfffffff;
//MutexLockGuard loo(CntLock);
//CntLock.lock();
while(i--);
printf("%d\n", ++cnt);
//CntLock.unlock();
sleep(1);
}
int main()
{
//ThreadPool Test
ThreadPool tp(5, 2);
tp.start();
sleep(3);
for(int i = 0; i < 8; i++)
tp.addTask(test);
getchar();
return 0;
}
简单test结果:
thread 140068496353024 run task
thread 140068504745728 run task
1
2
thread 140068504745728 run task
thread 140068496353024 run task
3
4
thread 140068496353024 run task
thread 140068504745728 run task
5
6
thread 140068496353024 run task
thread 140068504745728 run task
7
8
linux通过c++实现线程池类的更多相关文章
- linux 条件变量与线程池
条件变量Condition Variables 概述 1. 条件变量提供了另外一种线程同步的方式.如果没有条件变量,程序需要使用线程连续轮询(可能在临界区critical section内)方式检查条 ...
- java中的线程(3):线程池类 ThreadPoolExecutor「线程池的类型、参数、扩展等」
官方文档: https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html 1.简介 pu ...
- 【Java 多线程】Java线程池类ThreadPoolExecutor、ScheduledThreadPoolExecutor及Executors工厂类
Java中的线程池类有两个,分别是:ThreadPoolExecutor和ScheduledThreadPoolExecutor,这两个类都继承自ExecutorService.利用这两个类,可以创建 ...
- 在Linux下写一个线程池以及线程池的一些用法和注意点
-->线程池介绍(大部分来自网络) 在这个部分,详细的介绍一下线程池的作用以及它的技术背景以及他提供的一些服务等.大部分内容来自我日常生活中在网络中学习到的一些概念性的东西. -->代码 ...
- LINUX下的简单线程池
前言 任何一种设计方式的引入都会带来额外的开支,是否使用,取决于能带来多大的好处和能带来多大的坏处,好处与坏处包括程序的性能.代码的可读性.代码的可维护性.程序的开发效率等. 线程池适用场合:任务比较 ...
- 一个简单的linux线程池(转-wangchenxicool)
线程池:简单地说,线程池 就是预先创建好一批线程,方便.快速地处理收到的业务.比起传统的到来一个任务,即时创建一个线程来处理,节省了线程的创建和回收的开销,响应更快,效率更高. 在linux中,使用的 ...
- Linux下通用线程池的创建与使用
线程池:简单地说,线程池 就是预先创建好一批线程,方便.快速地处理收到的业务.比起传统的到来一个任务,即时创建一个线程来处理,节省了线程的创建和回收的开销,响应更快,效率更高. 在linux中,使用的 ...
- java多线程系类:JUC线程池:02之线程池原理(一)
在上一章"Java多线程系列--"JUC线程池"01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我 ...
- Linux C++线程池
.为什么需要线程池? 部分应用程序需要执行很多细小的任务,对于每个任务都创建一个线程来完成,任务完成后销毁线程,而这就会产生一个问题:当执行的任务所需要的时间T1小于等于创建线程时间T2和销毁线程时间 ...
随机推荐
- angularJS $watch $apply $digest
看O'Reilly的书看到$watch这部分,不过没看懂,网上很多资料也含糊不清,不过还是找到了几个好的,简单记录一下. 一句话说明,$watch是用来监视变量的,好了直接上代码 <html&g ...
- 谈谈Flash图表中数据的采集
一般来说flash中的数据是不能被现有技术很容易采集到的,但是也不能谈flash色变,要具体问题具体分析,有些flash是可以通过一些分析发现背后的数据.然后采集就变得很容易了. 具体案例:搜房房价走 ...
- Sparse AutoEncoder简介
1. AutoEncoder AutoEncoder是一种特殊的三层神经网络, 其输出等于输入:\(y^{(i)}=x^{(i)}\), 如下图所示: 亦即AutoEncoder想学到的函数为\(f_ ...
- Docker 初相见
Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源. Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然后发布到任何流行的 Li ...
- debian旧日笔记
18:45 2007-10-7 序 图形界面较好的Linux操作系统有三个主要的发行版本:RedHat, Debian, SuSE.自RedHat9后,RedHat以Fedora为名发行新的版本.仍然 ...
- java与Excel (.xls文件) ---使用JXL创建,增添表格文件
由于一些原因要搞一下excel文件,个人感觉poi太难,所以用了JXL(感觉比较简单). 1.添加外部归档 jxl.jar 2. /** 生成的xls文件第一次需要手动选择EXCEL打开* * */ ...
- Java编程思想 4th 第3章 操作符
有了数据,还需要进行数据间的运算,因此Java中也有数据间运算的各种符号,书本称之为操作符,正确的翻译应该是运算符. Java中的运算符同C++相同,运算符同运算符对象构成表达式,表达式是运算对象及运 ...
- [转]QVector与QByteArray——Qt的写时复制(copy on write)技术
我们在之前的博文QVector的内存分配策略与再谈QVector与std::vector——使用装饰者让std::vector支持连续赋值中简单聊了聊QVector内存分配和赋值方面的一点东西,今天接 ...
- RPC简介与hdfs读过程与写过程简介
1.RPC简介 Remote Procedure Call 远程过程调用协议 RPC——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.RPC协议假定某些 ...
- redis安装(linux)
一.redis安装步骤 1.yum install gcc 如果你机器已经安装了编译环境请忽略,否则在使用make编译源码时会报错. 报错信息:make: *** [adlist.o] 2.使用w ...