读本文之前,请务必阅读:

使用C++11的function/bind组件封装Thread以及回调函数的使用

Linux组件封装(五)一个生产者消费者问题示例

 

线程池本质上是一个生产者消费者模型,所以请熟悉这篇文章: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的更多相关文章

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

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

  2. 线程池ThreadPool的常用方法介绍

    线程池ThreadPool的常用方法介绍 如果您理解了线程池目的及优点后,让我们温故下线程池的常用的几个方法: 1. public static Boolean QueueUserWorkItem(W ...

  3. Python之路(第四十六篇)多种方法实现python线程池(threadpool模块\multiprocessing.dummy模块\concurrent.futures模块)

    一.线程池 很久(python2.6)之前python没有官方的线程池模块,只有第三方的threadpool模块, 之后再python2.6加入了multiprocessing.dummy 作为可以使 ...

  4. C#多线程学习 之 线程池[ThreadPool](转)

    在多线程的程序中,经常会出现两种情况: 一种情况:   应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应                   这一般使用ThreadPo ...

  5. 高效线程池(threadpool)的实现

    高效线程池(threadpool)的实现 Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线 ...

  6. 多线程Thread,线程池ThreadPool

    首先我们先增加一个公用方法DoSomethingLong(string name),这个方法下面的举例中都有可能用到 #region Private Method /// <summary> ...

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

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

  8. C#多线程学习 之 线程池[ThreadPool]

    在多线程的程序中,经常会出现两种情况: 一种情况:   应用程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应                   这一般使用ThreadPo ...

  9. 线程池ThreadPool实战

    线程池ThreadPool 线程池概念 常用线程池和方法 1.测试线程类 2.newFixedThreadPool固定线程池 3.newSingleThreadExecutor单线程池 4.newCa ...

随机推荐

  1. 自定义View Measure过程(2)

    目录 目录 1. 作用 测量View的宽/高 在某些情况下,需要多次测量(measure)才能确定View最终的宽/高: 在这种情况下measure过程后得到的宽/高可能是不准确的: 建议在layou ...

  2. 由做网站操作日志想到的HttpModule应用

    背景 在以前的Web项目中,记录用户操作日志,总是在方法里,加一行代码,记录此时用户操作类型与相关信息.该记录日志的方法对原来的业务操作侵入性较强,也比较零散,不便于查看和管理.那么有没有更加通用点的 ...

  3. OS X升级到10.10之后使用pod出现问题的解决方法

     http://blog.csdn.net/dqjyong/article/details/37958067 OS X升级到10.10之后使用pod出现问题的解决方法 分类: IOS2014-07-1 ...

  4. 使用timeit模块 测试两种方式生成列表的所用的时间

    from timeit import Timer def test(): li=[] for i in range(10000): li.append(i) def test2(): li=[i fo ...

  5. js中立即执行函数写法理解

    在理解了一些函数基本概念后,回头看看( function(){…} )()和( function (){…} () )这两种立即执行函数的写法,最初我以为是一个括号包裹匿名函数, 并后面加个括号立即调 ...

  6. 通栏导航栏的制作,综合使用CSS属性,代码不超过30行

    这篇文章,小编带领大家一同做一个利用CSS技术实现的导航栏.通过这个导航栏的制作,希望大家能够对前几篇文章中学习到的CSS属性能有一个整体的认识,并能够达到灵活运用的程度. 承接文章:灵活控制块级元素 ...

  7. ActiveMQ StartUp

    从http://activemq.apache.org/activemq-5132-release.html 下载 解压 从apache-activemq-5.13.2\bin\win64\wrapp ...

  8. 在windows 10 64bit系统上安装python 3.6 64bit的numpy模块

    1.查找自己的python版本对照的whl文件(cp36代表的是版本) 地址:https://pypi.python.org/pypi/numpy 2.下载完毕执行一下命令即可 pip install ...

  9. USACO 4.4.2 追查坏牛奶 oj1341 网络流最小割问题

    描述 Description 你第一天接手三鹿牛奶公司就发生了一件倒霉的事情:公司不小心发送了一批有三聚氰胺的牛奶.很不幸,你发现这件事的时候,有三聚氰胺的牛奶已经进入了送货网.这个送货网很大,而且关 ...

  10. 几个有用的PHP.ini配置项-路径和目录

    几个有用的PHP.ini配置项-路径和目录 路径和目录1.include_path = string作用域:PHP_INI_ALL默认值:NULL此参数指定的路径是include().require( ...