使用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 ...
随机推荐
- gvim设置使用
最近有一款编辑器叫sublimeText 2比较流行,我也下载用了一下,确实很好看,自动完成,缩进功能什么的也比较齐全,插件也十分丰富.但用起来不是很顺手,最后还是回到了Gvim(Vim的GUI版本, ...
- [bzoj4513][SDOI2016]储能表——数位dp
题目大意 求 \[\sum_{i = 0}^{n-1}\sum_{j=0}^{m-1} max((i\ xor\ j)\ -\ k,\ 0)\ mod\ p\] 题解 首先,开始并没有看出来这是数位d ...
- 信息传递(NOIP2015)(寻找最小环。。)
原题传送门 这是一道寻找最小环的题目. 在做的时候给每一个点染色.. 防止再做已经搜过的点(优化) v[]表示是否访问的过,以及第一次访问该点的时间. u[]表示染色.. 这道题还可以用拓补排序做. ...
- 杭电oj2047-2049、2051-2053、2056、2058
2047 阿牛的EOF牛肉串 #include<stdio.h> int main(){ int n,i; _int64 s[]; while(~scanf("%d" ...
- ios 改变图片大小缩放方法
http://www.cnblogs.com/zhangdadi/archive/2012/11/17/2774919.html http://bbs.csdn.net/topics/39089858 ...
- tf一些理解(根据资料)
首先看了开源操作机器人系统-ros这本书(张建伟)第五章slam导航 5.1使用tf配置机器人 还有ros navigation 教程 http://wiki.ros.org/navigation/T ...
- Informix 启动 Fatal error in shared memory initialization解决方法
https://blog.csdn.net/cy309173854/article/details/54929735
- Ionic 存储目录 CORS
使用不同的存储库结构官方为 { "scripts": { "install": "cd path-to/your-app && npm ...
- J.U.C并发框架源码阅读(十五)CopyOnWriteArrayList
基于版本jdk1.7.0_80 java.util.concurrent.CopyOnWriteArrayList 代码如下 /* * Copyright (c) 2003, 2011, Oracle ...
- HDU 5915 The Fastest Runner Ms. Zhang (CCPC2016 长春 E题,分类讨论 + 求字典序最小的直径 + 数据结构寻找最小值)
题目链接 CCPC2016 Changchun Problem E 题意 给定一个$n$个点$n$条边的无向图,现在从某一点$s$出发,每个点都经过一遍,最后在$t$点停止,经过的边数为$l$ ...