C++11 有很多新特性,但是很多特性只限于记过背过,最近看到有一个写的非常简单(100 来行)并且用了很多 C++ 新特性的 ThreadPool,写一篇笔记记录一下
Overview

Everything is function
- 每一个 task 都可以封装成一个 这样的函数,他们拥有不同的 以及不同的参数类型以及数量,如何将所有不同的 task 都归一化后放到容器里面?(类似于虚函数构造可以在一个 下挂所有不同的子类)
- 这里用 来将一个具体的函数绑定到一个 上,这样所有的任务最后都会归入 的容器中,也就是 ThreadPool 的消息队列
class ThreadPool {
private:
// need to keep track of threads so we can join them
std::vector< std::thread > workers;
// the task queue
std::queue< std::function<void()> > tasks;
...
}
未知定义未知参数量的模板类
- 我们在定义模板类时需要用到不定参数 Args...,
...定为展开左边内容多次,e.g. Args... 表示展开类型定义 F(int, int), args... 展开具体的值 - 我们在定义 时需要知道这个 task 异步执行返回的类型 T,可以用 来得到所需的类型 T
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared< std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
// 放入 task 到 tasks 队列
}
右值引用
- 上面代码的所有 & 都是
&&,右值引用主要是为了保证传入参数时如果是临时值,就直接转让所有权,而如果是左值,则只是执行复制操作,不需要重定义多类型,比如说 - 表示完美转发,如果 value 是左值,则复制;如果为右值,则直接转移所有权,避免重新拷贝一次。里面本质是包装了一个条件分支 , 里面本质是一个强制类型转换,将左值变为右值引用
T&&
Mutex
- unique_lock<mutex> + condition_variable 能用来表示一个经典的生产者消费者模型
- unique_lock 本质是 mutex 类的一个 RALL 的封装,而 condition_variable 的目的则是为了避免消费者需要频繁地自己拿 mutex 去 check condition。它最重要的就是提供了一个 bell 的服务,底层是 Linux 里面的 Futex 机制,实现了 的原子操作
- condition_variable 唤醒后依旧需要 check 满足状态,因为可能有多个唤醒线程竞争,同时 cv 可以满足“只起来看看”这种语义存在
// 参考
std::queue<int> q;
std::mutex m;
std::condition_variable cv;
void producer() {
{
std::lock_guard<std::mutex> lock(m);
q.push(1);
}
cv.notify_one();
}
void consumer() {
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [] { return !q.empty(); });
int x = q.front();
q.pop();
}
int main() {
std::thread p(producer), c(consumer);
p.join();
c.join();
}
源代码
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
class ThreadPool {
public:
ThreadPool(size_t);
template<class F, class... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>;
~ThreadPool();
private:
// need to keep track of threads so we can join them
std::vector< std::thread > workers;
// the task queue
std::queue< std::function<void()> > tasks;
// synchronization
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
: stop(false)
{
for(size_t i = 0;i<threads;++i)
workers.emplace_back(
[this]
{
for(;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock,
[this]{ return this->stop || !this->tasks.empty(); });
if(this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
}
);
}
// add new work item to the pool
template<class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>
{
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared< std::packaged_task<return_type()> >(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
// don't allow enqueueing after stopping the pool
if(stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task](){ (*task)(); });
}
condition.notify_one();
return res;
}
// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for(std::thread &worker: workers)
worker.join();
}
#endif