1.线程池

1.1 线程池是什么?

一种线程管理方式。

1.2 为什么用线程池?

线程的创建和销毁都需要消耗系统开销,当线程数量过多,系统开销过大,就会影响缓存局部性和整体性能。而线程池能够在充分利用内核资源的前提下,避免系统资源被过度调用。

1.3 如何设计线程池?

简单来说,在线程池中提前创建好多个线程,使用时从线程池中取出,使用完放回线程池。线程池中的线程调度由线程池中的管理者线程调度。

2.基于C++11的实现

Talk is cheap. Show me the code.

直接看程序,原理、函数在后面再介绍。

2.1 程序

程序主要分为四个文件,分别为:

  • Task.h //任务类
  • ThreadPool.h //线程池类
  • ThreadPool.cpp //线程池类实现
  • main.cpp //测试程序

2.1.2 任务类Task.h

#pragma once
using callback = void(*)(void*);//函数指针,定义别名 class Task{
public:
callback func;//回调任务函数
void* arg; //函数参数
public:
Task() { //无参构造函数
this->func = nullptr;
this->arg = nullptr;
}
Task(callback func, void* arg) {//含参构造函数
this->func = func;
this->arg = arg;
}
~Task() = default; //析构函数
Task(const Task &t) = default; //拷贝构造函数
Task& operator=(const Task &t); //拷贝赋值操作符
Task(Task &&t) = default; //移动构造函数,注意不能有const
Task& operator=(const Task &&t);//移动赋值操作符
};

2.1.2 线程池类ThreadPool.h

#pragma once

#include "Task.h"
#include <thread>
#include <queue>
#include <vector>
#include <atomic>
#include <mutex>
#include <condition_variable> using namespace std; class ThreadPool {
public:
ThreadPool(int minSize, int maxSize);//构造函数
void AddTask(Task task); //添加新任务
int GetBusyNum(); //获取当前工作中的线程数
int GetAliveNum(); //获取当前活着的线程数
int GetTaskQueueSize(); //获取当前任务队列长度
~ThreadPool(); ThreadPool(const ThreadPool &t) = default; //拷贝构造函数
ThreadPool& operator=(const ThreadPool &t); //拷贝赋值操作符
ThreadPool(ThreadPool &&t) = default; //移动构造函数
ThreadPool& operator=(const ThreadPool &&t);//移动赋值操作符 private:
queue<Task> taskQueue; //任务队列
thread managerID;//管理者线程ID
vector<thread> threadIDs;//工作中的线程组ID
int minNum;//最小线程数量(如果线程池中线程的数目过少,处理器的一些核可能就无法充分利用,浪费)
int maxNum;//最大线程数量(如果线程池中线程的数量过多,最终它们会竞争稀缺的处理器和内存资源,浪费大量的时间在上下文切换上。)
atomic_int busyNum;//工作中的线程数量(atomic_int保证其赋值,取值操作的原子性)
atomic_int liveNum;//活着的线程数量
atomic_int exitNum;//将要被销毁的线程数量 mutex mutexPool;//线程池的锁
condition_variable cond;//条件变量
bool shutDown;//是不是要销毁线程池, 销毁为true, 不销毁为false static void worker(void* arg);//工作的线程任务函数
static void manager(void* arg);//管理者线程任务函数 static const int NUMBER = 2;//管理者线程每次增加/销毁的线程数
};

2.1.3 线程池类实现ThreadPool.cpp

#include "ThreadPool.h"
#include <unistd.h> //pthread_self
#include <iostream> using namespace std; ThreadPool::ThreadPool(int minSize, int maxSize) {
do{
minNum = minSize;
maxNum = maxSize;
busyNum = 0;
liveNum = minSize;
exitNum = 0;
shutDown = false; //初始化管理者线程和工作线程组
managerID = thread(manager, this);
threadIDs.resize(maxSize);
for(int i = 0; i < minSize; ++i) {
threadIDs[i] = thread(worker, this);
} return;
} while(0);//do{...}while(0)结构提高代码健壮性
} ThreadPool::~ThreadPool() {
shutDown = true;
if(managerID.joinable()) {//阻塞在管理者线程,直到其执行完,再向下进行
managerID.join();
}
cond.notify_all();//唤醒所有等待的线程
for(int i = 0; i < maxNum; ++i) {//依次执行工作者的线程
if(threadIDs[i].joinable()) {
threadIDs[i].join();
}
}
} //添加新任务
void ThreadPool::AddTask(Task task) {
unique_lock<mutex> poolLock(mutexPool);
if(shutDown) {
return;
}
taskQueue.emplace(task);
cond.notify_all();
} int ThreadPool::GetBusyNum() {
return busyNum;
} int ThreadPool::GetAliveNum() {
return liveNum;
} int ThreadPool::GetTaskQueueSize() {
unique_lock<mutex> poolLock(mutexPool);
int queueSize = taskQueue.size();
poolLock.unlock();
return queueSize;
} //工作者线程
void ThreadPool::worker(void* arg) {
ThreadPool* pool = static_cast<ThreadPool*>(arg);
while(true) {
unique_lock<mutex> poolLock(pool->mutexPool);
//若当前任务队列为空且线程池处于开启状态
while(pool->taskQueue.empty() && !pool->shutDown) {
pool->cond.wait(poolLock);//阻塞工作线程
//若存在待销毁线程
if(pool->exitNum > 0) {
--pool->exitNum;
if(pool->liveNum > pool->minNum) {//若活着的线程数大于最小线程数,则可以进行销毁
--pool->liveNum;
cout << "threadID: " << pthread_self() << " has exited." << endl;
return;
}
}
} //判断线程池是否关闭了
if(pool->shutDown) {
cout << "threadID: " << pthread_self() << " has exited." << endl;
return;
} //从任务队列中取出一个任务
Task task = pool->taskQueue.front();
pool->taskQueue.pop();
++pool->busyNum; //解锁
poolLock.unlock(); //执行任务
cout << "threadID: " << pthread_self() << " start to work." << endl;
task.func(task.arg);
task.arg = nullptr; //执行完后,工作线程数-1
cout << "threadID: " << pthread_self() << " stop working." << endl;
--pool->busyNum;
}
} //管理者线程
void ThreadPool::manager(void* arg) {
ThreadPool* pool = static_cast<ThreadPool*>(arg);
while(!pool->shutDown) {
//每隔3秒检测一次
sleep(3); //添加新线程
//若任务个数大于活着的线程数,且活着的线程数小于最大线程数
if(pool->GetTaskQueueSize() > pool->liveNum && pool->liveNum < pool->maxNum) {
unique_lock<mutex> poolLock(pool->mutexPool);
poolLock.lock();
int count = 0;
for(int i = 0; i < pool->maxNum && count < ThreadPool::NUMBER && pool->liveNum < pool->maxNum; ++i) {
if(pool->threadIDs[i].get_id() == thread::id()) {
cout << "Create a new thread." << endl;
pool->threadIDs[i] = thread(worker, pool);
++count;
++pool->liveNum;
}
}
poolLock.unlock();
} //销毁线程
//若忙的线程*2小于存活的线程数,且存活的线程数大于最小的线程数
if(pool->busyNum * 2 < pool->liveNum && pool->liveNum > pool->minNum) {
pool->exitNum = ThreadPool::NUMBER;
for(int i = 0; i < ThreadPool::NUMBER; ++i) {//让工作的线程自杀
pool->cond.notify_all();
}
}
}
}

2.2 测试方法:

将上述文件放在Linux下的一个文件夹(我这里是\Share\study_threadPool\myself)

  • 进入该文件夹:cd /share/study_threadPool/myself/
  • 编译:g++ main.cpp ThreadPool.cpp -o ThreadPool.o -pthread
  • 运行:./ThreadPool.o

2.2 C++11相关函数

  1. thread类
  • ThreadPool.cpp第17行:managerID = thread(manager, this);表示创建一个新线程,manager是该线程执行的函数,this是该线程执行函数的参数。
  • ThreadPool.cpp第29行:managerID.joinable() 判断该线程是否可以join
  • ThreadPool.cpp第30行:managerID.join() 阻塞在该线程,直到其执行完
  • ThreadPool.cpp第123行:pool->threadIDs[i].get_id()表示获取该线程的ID
  1. mutex
  • ThreadPool.cpp第42行:unique_lock<mutex> poolLock(mutexPool); 自动加锁与解锁
  • ThreadPool.cpp第61行:poolLock.unlock();解锁
  • ThreadPool.cpp第120行:poolLock.lock();加锁
  1. condition_variable
  • ThreadPool.cpp第32行:nd.notify_all();唤醒所有等待的线程
  1. atomic
  • ThreadPool.h第34行:atomic_int busyNum;本质还是int,只是每次对其操作时,都能保证是原子操作
  1. using
  • Task.h第2行:sing callback = void(*)(void*);函数的别名

3.调试过程中出现的问题及解决方法

3.1 warning:#pragma once in main file



解决方案:g++编译时不要编译头文件

3.2 移动构造函数出错



解决方案:移动构造函数的参数不能加const

4.参考

基于C++11的线程池实现的更多相关文章

  1. 基于C++11实现线程池的工作原理

    目录 基于C++11实现线程池的工作原理. 简介 线程池的组成 1.线程池管理器 2.工作线程 3.任务接口, 4.任务队列 线程池工作的四种情况. 1.主程序当前没有任务要执行,线程池中的任务队列为 ...

  2. 基于C++11的线程池,简洁且可以带任意多的参数

    咳咳.C++11 加入了线程库,从此告别了标准库不支持并发的历史.然而 c++ 对于多线程的支持还是比较低级,稍微高级一点的用法都需要自己去实现,譬如线程池.信号量等.线程池(thread pool) ...

  3. 基于C++11的线程池(threadpool),简洁且可以带任意多的参数

    咳咳.C++11 加入了线程库,从此告别了标准库不支持并发的历史.然而 c++ 对于多线程的支持还是比较低级,稍微高级一点的用法都需要自己去实现,譬如线程池.信号量等.线程池(thread pool) ...

  4. 基于C++11的线程池

    1.封装的线程对象 class task : public std::tr1::enable_shared_from_this<task> { public: task():exit_(f ...

  5. 基于C++11的数据库连接池实现

    0.注意 该篇文章为了让大家尽快看到效果,代码放置比较靠前,看代码前务必看下第4部分的基础知识. 1.数据库连接池 1.1 是什么? 数据库连接池负责分配.管理和释放数据库连接,属于池化机制的一种,类 ...

  6. 基于ThreadPoolExecutor,自定义线程池简单实现

    一.线程池作用 在上一篇随笔中有提到多线程具有同一时刻处理多个任务的特点,即并行工作,因此多线程的用途非常广泛,特别在性能优化上显得尤为重要.然而,多线程处理消耗的时间包括创建线程时间T1.工作时间T ...

  7. 11 java 线程池 实现原理

    一 关键类的实现 1 ThreadPoolExecutor类 java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的 ...

  8. 11 java 线程池 使用实例

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

  9. 使用C++11封装线程池ThreadPool

    读本文之前,请务必阅读: 使用C++11的function/bind组件封装Thread以及回调函数的使用 Linux组件封装(五)一个生产者消费者问题示例   线程池本质上是一个生产者消费者模型,所 ...

随机推荐

  1. Spring源码分析笔记--AOP

    核心类&方法 BeanDefinition Bean的定义信息,封装bean的基本信息,从中可以获取类名.是否是单例.是否被注入到其他bean中.是否懒加载.bean依赖的bean的名称等. ...

  2. 学习heartbeat-02安装及配置

    2.部署Heartbeat高可用需求 2.1 操作系统 CentOS-6.8-x86_64 2.2 Heartbeat服务主机资源准备 主服务器A: 主机名:heartbeat-1-130 eth0网 ...

  3. js获取一周前日期

    项目中需要设定默认开始时间为一周前,结束时间为现在,现在写一下如何用js获取一周前日期. 1 var time=(new Date).getTime()-7*24*60*60*1000; 2 var ...

  4. 关于css布局、居中的问题以及一些小技巧

    CSS的两种经典布局 左右布局 一栏定宽,一栏自适应 <!-- html --> <div class="left">定宽</div> < ...

  5. 《深入理解ES6》笔记——扩展对象的功能性(4)

    变量功能被加强了.函数功能被加强了,那么作为JavaScript中最普遍的对象,不加强对得起观众吗? 对象类别 在ES6中,对象分为下面几种叫法.(不需要知道概念) 1.普通对象 2.特异对象 3.标 ...

  6. 彻底搞懂CSS层叠上下文、层叠等级、层叠顺序、z-index

    前言 最近,在项目中遇到一个关于CSS中元素z-index属性的问题,具体问题不太好描述,总结起来就是当给元素和父元素色设置position属性和z-index相关属性后,页面上渲染的元素层级结果和我 ...

  7. java中StringBuffer的用法

    2.StringBuffer StringBuffer:String类同等的类,它允许字符串改变(原因见上一段所说).Overall, this avoids creating many tempor ...

  8. java对象有什么重要的?

    3.历史上讲,对象有什么重要的?  [新手可忽略不影响继续学习]早期的编程主要是面向过程的编程,处理的问题都相对的简单,比较过程化,换句话说,就是一步一步从开始到结束,比如第一步进入电梯,第二步关门, ...

  9. 字符串反转&说反话

    题目描述 写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串.(字符串长度不超过1000) 输入描述: 输入N个字符 输出描述: 输出该字符串反转后的字符串 示例1 输入 abcd 输出 d ...

  10. 记住用户名和登录密码+虚拟机没有root权限解决办法

    今日所学: 记住用户名和登录密码 用adb查看保存文件内容 如何使用adb 如何安装adb-百度经验 遇到的问题: 用adb查看文件时,没有权限访问data文件 出现原因:google play虚拟机 ...