使用C++11封装线程池ThreadPool
读本文之前,请务必阅读:
使用C++11的function/bind组件封装Thread以及回调函数的使用
线程池本质上是一个生产者消费者模型,所以请熟悉这篇文章:Linux组件封装(五)一个生产者消费者问题示例。
在ThreadPool中,物品为计算任务,消费者为pool内的线程,而生产者则是调用线程池的每个函数。
搞清了这一点,我们很容易就需要得出,ThreadPool需要一把互斥锁和两个同步变量,实现同步与互斥。
存储任务,当然需要一个任务队列。
除此之外,我们还需要一系列的Thread,因为Thread无法复制,所以我们使用unique_ptr作为一个中间层。
所以Thread的数据变量如下:
class ThreadPool : boost::noncopyable
{
public:
typedef std::function<void ()> Task; ThreadPool(size_t queueSize, size_t threadsNum);
~ThreadPool(); void start();
void stop(); void addTask(Task task); //C++11
Task getTask(); bool isStarted() const { return isStarted_; } void runInThread(); private:
mutable MutexLock mutex_;
Condition empty_;
Condition full_; size_t queueSize_;
std::queue<Task> queue_; const size_t threadsNum_;
std::vector<std::unique_ptr<Thread> > threads_;
bool isStarted_;
};
显然,我们使用了function,作为任务队列的任务元素。
构造函数的实现较简单,不过,之前务必注意元素的声明顺序与初始化列表的顺序相一致。
ThreadPool::ThreadPool(size_t queueSize, size_t threadsNum)
: empty_(mutex_),
full_(mutex_),
queueSize_(queueSize),
threadsNum_(threadsNum),
isStarted_(false)
{ }
添加和取走任务是生产者消费者模型最核心的部分,但是套路较为固定,如下:
void ThreadPool::addTask(Task task)
{
MutexLockGuard lock(mutex_);
while(queue_.size() >= queueSize_)
empty_.wait();
queue_.push(std::move(task));
full_.notify();
} ThreadPool::Task ThreadPool::getTask()
{
MutexLockGuard lock(mutex_);
while(queue_.empty())
full_.wait();
Task task = queue_.front();
queue_.pop();
empty_.notify();
return task;
}
注意我们的addTask使用了C++11的move语义,在传入右值时,可以提高性能。
还有一些老生常谈的问题,例如:
wait前加锁
使用while循环判断wait条件(为什么?)
要想启动线程,需要给Thread提供一个回调函数,编写如下:
void ThreadPool::runInThread()
{
while(1)
{
Task task(getTask());
if(task)
task();
}
}
就是不停的取走任务,然后执行。
OK,有了线程的回调函数,那么我们可以编写start函数。
void ThreadPool::start()
{
isStarted_ = true;
//std::vector<std::unique<Thread> >
for(size_t ix = 0; ix != threadsNum_; ++ix)
{
threads_.push_back(
std::unique_ptr<Thread>(
new Thread(
std::bind(&ThreadPool::runInThread, this))));
}
for(size_t ix = 0; ix != threadsNum_; ++ix)
{
threads_[ix]->start();
} }
这里较难理解的是线程的创建,Thread内存放的是std::unique_ptr<Thread>,而ptr的创建需要使用new动态创建Thread,Thread则需要在创建时,传入回调函数,我们采用bind适配runInThread的参数值。
这里我们采用C++11的unique_ptr,成功实现vector无法存储Thread(为什么?)的问题。
我们的第一个版本已经编写完毕了。
添加stop功能
刚才的ThreadPool只能启动,无法stop,我们从几个方面着手,利用bool变量isStarted_,实现正确退出。
改动的有以下几点:
首先是Thread的回调函数不再是一个死循环,而是:
void ThreadPool::runInThread()
{
while(isStarted_)
{
Task task(getTask());
if(task)
task();
}
}
然后addTask和getTask,在while循环判断时,加入了bool变量:
void ThreadPool::addTask(Task task)
{
MutexLockGuard lock(mutex_);
while(queue_.size() >= queueSize_ && isStarted_)
empty_.wait(); if(!isStarted_)
return; queue_.push(std::move(task));
full_.notify();
} ThreadPool::Task ThreadPool::getTask()
{
MutexLockGuard lock(mutex_);
while(queue_.empty() && isStarted_)
full_.wait(); if(!isStarted_) //线程池关闭
return Task(); //空任务 assert(!queue_.empty());
Task task = queue_.front();
queue_.pop();
empty_.notify();
return task;
}
这里注意,退出while循环后,需要再判断一次bool变量,因为未必是条件满足了,可能是线程池需要退出,调整了isStarted变量。
最后一个关键是我们的stop函数:
void ThreadPool::stop()
{
if(isStarted_ == false)
return; {
MutexLockGuard lock(mutex_);
isStarted_ = false;
//清空任务
while(!queue_.empty())
queue_.pop();
}
full_.notifyAll(); //激活所有的线程
empty_.notifyAll(); for(size_t ix = 0; ix != threadsNum_; ++ix)
{
threads_[ix]->join();
}
threads_.clear();
}
这里有几个关键:
先将bool设置为false,然后调用notifyAll,激活所有等待的线程(为什么)。
最后我们总结下ThreadPool关闭的流程:
1.isStarted设置为false
2.加锁,清空队列
3.发信号激活所有线程
4.正在运行的Thread,执行到下一次循环,退出
5.正在等待的线程被激活,然后while判断为false,执行到下一句,检查bool值,然后退出。
6.主线程依次join每个线程。
7.退出。
最后补充下析构函数的实现:
ThreadPool::~ThreadPool()
{
if(isStarted_)
stop();
}
完毕。
使用C++11封装线程池ThreadPool的更多相关文章
- 基于C++11的线程池(threadpool),简洁且可以带任意多的参数
咳咳.C++11 加入了线程库,从此告别了标准库不支持并发的历史.然而 c++ 对于多线程的支持还是比较低级,稍微高级一点的用法都需要自己去实现,譬如线程池.信号量等.线程池(thread pool) ...
- 线程池ThreadPool的常用方法介绍
线程池ThreadPool的常用方法介绍 如果您理解了线程池目的及优点后,让我们温故下线程池的常用的几个方法: 1. public static Boolean QueueUserWorkItem(W ...
- Python之路(第四十六篇)多种方法实现python线程池(threadpool模块\multiprocessing.dummy模块\concurrent.futures模块)
一.线程池 很久(python2.6)之前python没有官方的线程池模块,只有第三方的threadpool模块, 之后再python2.6加入了multiprocessing.dummy 作为可以使 ...
- C#多线程学习 之 线程池[ThreadPool](转)
在多线程的程序中,经常会出现两种情况: 一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应 这一般使用ThreadPo ...
- 高效线程池(threadpool)的实现
高效线程池(threadpool)的实现 Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线 ...
- 多线程Thread,线程池ThreadPool
首先我们先增加一个公用方法DoSomethingLong(string name),这个方法下面的举例中都有可能用到 #region Private Method /// <summary> ...
- 基于C++11实现线程池的工作原理
目录 基于C++11实现线程池的工作原理. 简介 线程池的组成 1.线程池管理器 2.工作线程 3.任务接口, 4.任务队列 线程池工作的四种情况. 1.主程序当前没有任务要执行,线程池中的任务队列为 ...
- C#多线程学习 之 线程池[ThreadPool]
在多线程的程序中,经常会出现两种情况: 一种情况: 应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应 这一般使用ThreadPo ...
- 线程池ThreadPool实战
线程池ThreadPool 线程池概念 常用线程池和方法 1.测试线程类 2.newFixedThreadPool固定线程池 3.newSingleThreadExecutor单线程池 4.newCa ...
随机推荐
- Linux装软件
一.rpm包安装方式步骤: 1.找到相应的软件包,比如soft.version.rpm,下载到本机某个目录: 2.打开一个终端,su -成root用户: 3.cd soft.version.rpm所在 ...
- CodeVs1515 跳
题目描述 Description 邪教喜欢在各种各样空间内跳. 现在,邪教来到了一个二维平面.在这个平面内,如果邪教当前跳到了(x,y),那么他下一步可以选择跳到以下4个点:(x-1,y), (x+1 ...
- poj2728 最小比率生成树——01分数规划
题目大意: 有n个村庄,村庄在不同坐标和海拔,现在要对所有村庄供水, 只要两个村庄之间有一条路即可,建造水管距离为坐标之间的欧几里德距离,费用为海拔之差, 现在要求方案使得费用与距离的比值最小,很显然 ...
- 汕头市队赛 SRM 08 C
C-3 SRM 08 描述 给一个图,n 个点 m 条双向边,每条边有其长度.n 个点中有 k 个是特殊点,问任意两个特殊点的最短路是多少. 输入格式 第一行三个整数 n m k 第二行 k 个整数 ...
- 无法安装MVC3,一直卡在vs10-kb2483190
原文发布时间为:2011-05-15 -- 来源于本人的百度文章 [由搬家工具导入] 无法安装MVC3,一直卡在vs10-kb2483190 解决方案: 1、用winrar 解压 MVC3安装文件 2 ...
- HDU 5159 Card (概率求期望)
B - Card Time Limit:5000MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Submit Sta ...
- PDF工具
PDF打印工具 pdfcreator 可以将所有文件都打印为pdf PDF 阅读-编辑-打印工具 Adobe Acrobat DC 可以将所有文件都打印为pdf,并且支持编辑PDF与阅读,可以将PDF ...
- 更改了mysql的配置文件之后,启动不了mysql服务
更改了mysql的配置文件之后,启动不了mysql服务 mysql数据库error: Found option without preceding group in config file 问题解决 ...
- Django和SQLAlchemy区别
译者注:本文首先介绍了什么是ORM,然后从多个方面对Python语言下的两个ORM库Django和SQLAlchemy进行比较,为ORM的选型提供了较为全面的指导建议.以下是译文. ORM是什么? 在 ...
- hdu 2489(状态压缩+最小生成树)
Minimal Ratio Tree Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Other ...