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

使用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. vim编辑器快捷运用

    vim下可以使用常用的箭头键 但是 还有其它键可以让你更快的达到目标 hjkl 这是代替箭头键功能的 H M L 跳到屏幕的顶上 中间 下方 w 跳到下一个单词的开始e 跳到单词的结束b 向后跳 gg ...

  2. windows7下检测耳机麦克拔插(转)

    原文转自 https://blog.csdn.net/rankun1/article/details/50972990 #include "stdafx.h" #define SA ...

  3. 使用clamav查杀病毒

    cd ~ wget http://www.zlib.net/fossils/zlib-1.2.8.tar.gz .tar.gz cd zlib- ./configure --prefix=/usr/l ...

  4. centos编译安装vim并支持lua

    系统环境:centos6.5 x86 (basic server) 1.安装编译环境. # yum groupinstall "Development Tools" 2.安装vim ...

  5. Centos 查看硬盘空间

    df -h du -ah --max-depth=1 目录深度为1 -h:以人类可读的方式显示 -a:显示目录占用的磁盘空间大小,还要显示其下目录和文件占用磁盘空间的大小 -s:显示目录占用的磁盘空间 ...

  6. Centos 6.3软件安装

    一.软件安装包的类型: 1. tar包,如software-1.2.3-1.tar.gz.它是使用UNIX系统的打包工具tar打包的. 2. rpm包,如software-1.2.3-1.i386.r ...

  7. css容器

    .s1{ background-color:pink; font-weight:bold; font-size=16px; color:black; } #id1{ background-color: ...

  8. React Native解决Android的WebView无法执行injectedJavaScript代码

    需求 在用WebView组件写一个东西,要求功能:打开web后进行js代码注入. 开发 代码很简单,示例: const js = ` alert(1); alert(2); `; <WebVie ...

  9. (20)C#泛型

    泛型的定义:通过参数化类型来实现在同一份代码上操作多种数据类型.泛型编程时一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用. 优点: 1.省去了拆箱.装箱 2.提高安全性 3. ...

  10. (15)C#集合

    http://blog.csdn.net/hcw_peter/article/details/3980723 集合分为非泛型集合和泛型集合 ,泛型集合可以指定放入集合中的类型. 一.非泛性集合 引用命 ...