作者申明:
1、以下线程池方案由本人(大谦世界)原创,公开且共享,转载请注明出处。
2、若用于实际工作中,产生的bug后果自负,本人概不负责!
3、方案实现可能并不完善,懂行的自然看得出来,后续可能会对部分接口重写。
4、对于方案代码的注释说明比较完善,懂的自然懂,不懂的注释再多也没用。
众所周知,C/C++标准库没有线程池,且似乎直到20标准仍然没有。因此,本人设计了该线程池方案,用于对C++并发的研究学习。
原理与方案:
1、初始化生成一批线程,类似于招聘一大批工人,所有人处于空闲状态,等待任务的到来。
2、生成一个任务队列,类似于控制中心,用于接收新任务。且接到任务后立马唤醒一个“空闲”状态的线程,让它开始工作。
3、线程池中的某个空闲线程接到任务后开始工作,工作完毕后进入空闲状态,等待下一次任务的到来。
4、如果所有线程都在工作中,则任务堆积起来慢慢处理,并不会丢掉任务。
5、可以对工人工厂(线程池)进行扩招或裁员,也就是随时动态化调整。
6、可以关闭控制中心,不再接收任务了。当然,也是可以重启控制中心的。
7、用户提交的任务,是可以等待执行结果的,这也是该线程池的特色之一。
8、基于C++11标准库开发,理论上C++14、17、20都支持。
9、线程池支持随时调整大小、暂停、重启、关闭等操作。
10、线程池中各线程都拥有3个状态:工作、空闲、死亡。死亡的线程不再工作且不占用系统资源。
11、线程池中各线程保持独立运行,互不干扰,对于外部共用的资源,需要自行加锁。
12、线程池中各线程在存活期间(工作或空闲),都是重复使用的,除非人为消灭。
线程池实现文件
/**
* @file z_pool.h
* @the header file of C++ threadPool.
* @author dqsjqian Mr.Zhang
* @mail dqsjqian@163.com
* @date Sep 01 2018
*/
#pragma once
#include <string>
#include <vector>
#include <list>
#include <queue>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
using namespace std;
#define MAX_THREAD_NUM 2048
//线程池,可以提交变参函数或lambda表达式的匿名函数执行,可以获取执行返回值
//不支持类成员函数, 支持类静态成员函数或全局函数,operator()函数等
class threadpool
{
private:
using Task = std::function<void()>;
// 线程池
std::vector<std::thread> pool;
// 是否关闭某线程
std::vector<bool> stoped;
// 任务队列
std::queue<Task> tasks;
// 同步
std::mutex tasks_lock, pool_mutex;
// 条件阻塞
std::condition_variable task_cond_var;
//空闲线程数量
std::atomic<unsigned int> idle_thread_num;
//工作线程数量
std::atomic<unsigned int> work_thread_num;
//是否让线程自生自灭
bool freedom_pool;
//线程池是否暂停工作了
bool pool_stoped;
private:
void _initPool_()
{
unsigned int index = pool.size();
stoped.emplace_back(false); idle_thread_num++;//pool容器即将push一个,空闲线程数++
pool.emplace_back([this, index] {//相对于容器的push_back、insert操作,更加高级
//此处的循环体不会阻塞整体,因为该处是单个线程的lambda函数,运行在单独的线程中
while (true)
{
std::function<void()> task;// 用于获取一个待执行的 task
{// unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock()
std::unique_lock<std::mutex> lock(this->tasks_lock);
this->task_cond_var.wait(lock, [this, index]
{
return this->stoped[index] || !this->tasks.empty();
}); // wait 直到有 task
if (this->stoped[index]) { idle_thread_num--; return; }
task = std::move(this->tasks.front()); // 取一个 task
this->tasks.pop();
}//出了该作用域,lock自动解锁
{ //此处idle_thread_num的--和++操作并不会影响成功启动的线程总数量,
//因为构造的时候,所有的子线程全在上面wait处等待,并不会执行到此处来
idle_thread_num--, work_thread_num++;
task();//耗时任务,执行完成后,可用线程数++
idle_thread_num++, work_thread_num--;
}
}}
);
}
unsigned int _checkSize_(unsigned int size)
{
size = size < 1 ? 1 : size;
size = size > MAX_THREAD_NUM ? MAX_THREAD_NUM : size;
return size;
}
public:
inline threadpool() { work_thread_num = 0; freedom_pool = false; pool_stoped = false; }
inline ~threadpool()
{
for (auto &a : stoped)a = true;
task_cond_var.notify_all(); // 唤醒所有线程执行
for (auto &th : pool) {
if (freedom_pool)th.detach(); // 让线程“自生自灭”
else if (th.joinable())
th.join(); // 等待任务结束, 前提:线程一定会执行完
}
}
public:
// 提交一个任务到线程池
// 返回一个任务的future,调用.get()可以获取返回值(如果有的话),且会阻塞并等待任务执行完
// 以下两种方法可以实现调用类成员函数:
// 1、使用bind: .commitTask(std::bind(&ZCQ::helloWorld, &zcq));
// 2、使用mem_fn: .commitTask(std::mem_fn(&ZCQ::helloWorld), &zcq);
template<typename FUNC, typename... Args>
auto commitTask(FUNC&& func, Args&&... args)->std::future<decltype(func(args...))>
{
if (hasStopedPool())throw std::runtime_error("the threadPool is stopped.");
// typename std::result_of<F(Args...)>::type, 函数 func 的返回值类型
using ReturnType = decltype(func(args...));
auto task = std::make_shared<std::packaged_task<ReturnType()> >(
std::bind(std::forward<FUNC>(func), std::forward<Args>(args)...));
std::future<ReturnType> future = task->get_future();
{// 添加任务到队列
std::lock_guard<std::mutex> lock(tasks_lock);//对当前块的语句加锁
tasks.emplace([task]() {(*task)(); });// emplace相当于容器的push操作,不会发生临时拷贝,更加高级
//tasks.push_back(std::move(task)); //跟上面那句写法一模一样
}
task_cond_var.notify_one(); // 唤醒一个线程去取获取任务
return future;
}
//size:线程池大小; freedom_threads:是否让线程池"自生自灭"
void begin(unsigned int size, bool freedom_threads = false)
{
pool_stoped = false;
freedom_pool = freedom_threads;
size = _checkSize_(size);
for (unsigned int s = 0; s < size; s++)_initPool_();
}
//重设线程池大小
void resize(unsigned int sz)
{
pool_stoped = false;
sz = _checkSize_(sz);
//活着的线程数
size_t as = idle_thread_num + work_thread_num;
//目前线程池总大小
size_t ps = pool.size(), rs = 0;
if (sz > as)for (unsigned int s = as; s < sz; s++)_initPool_();
if (sz < as)for (auto &s : stoped) { if (!s) { s = true, task_cond_var.notify_all(); rs++; }if (rs == as - sz)break; }
}
// 空闲线程数量
unsigned int idleNum() { return idle_thread_num; }
// 工作线程数量
unsigned int workNum() { return work_thread_num; }
// 暂时关闭任务提交
void stopTask() { pool_stoped = true; }
// 重启任务提交
void restartTask() { pool_stoped = false; }
// 强制关闭线程池,后续可用begin或resize重新开启
void close() { for (auto &a : stoped)a = true; task_cond_var.notify_all(); pool_stoped = true; }
// 是否停止了线程池的工作
bool hasStopedPool() { return pool_stoped; }
// 阻塞且等待所有任务执行完成
void waitAllTaskRunOver() { while (work_thread_num) {}; }
};
使用方法示例
/**
* @file z_pool.cpp
* @the sample file of C++ threadPool.
* @author dqsjqian Mr.Zhang
* @mail dqsjqian@163.com
* @date Sep 01 2018
*/
#include "z_pool.h"
#include <windows.h>
#include <iostream>
#include <exception>
using namespace std;
void mytask()
{
int a = 106, b = 49069;
int c = a * b;
}
//测试程序
void main()
{
//创建一个线程池对象
threadpool tp;
//启动线程池
tp.begin(150);
time_t t1 = clock();
while (true)
{
try
{
tp.commitTask(std::bind(&mytask));
time_t t2 = clock();
if (t2 - t1 == 10 * 1000)tp.resize(130);
if (t2 - t1 == 15 * 1000)tp.resize(160);
if (t2 - t1 == 20 * 1000)tp.resize(140);
if (t2 - t1 == 25 * 1000)tp.resize(180);
//在第30秒时停掉线程池,继续提交会引发异常,在catch中捕获
if (t2 - t1 == 30 * 1000)tp.stopTask();
}
catch (exception e)
{
cout << string(e.what());
tp.restartTask();//前面暂停了任务提交,现在重新开启
}
}
}
具体有哪些接口不多介绍,请自行阅读z_pool.h文件。如有bug或建议,请在评论区留言,谢谢。
第二版:
#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>
constexpr size_t maxQueueSize = 1024;
class ThreadPool {
public:
ThreadPool(size_t);
ThreadPool(const ThreadPool&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
~ThreadPool();
template<class F, class... Args>
decltype(auto) commitTask(F&& f, Args&&... args);
private:
std::vector<std::thread> workers;
std::queue<std::function<void()>> tasks;
std::condition_variable tasks_full_flag; // block commitTask if tasks queue is full
std::mutex queue_mutex;
std::condition_variable condition;
std::atomic_bool stop;
void newWorker();
};
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads = std::thread::hardware_concurrency()) : stop(false)
{
workers.reserve(threads);
for (size_t i = 0; i < threads; ++i)
newWorker();
}
// add new work item to the pool
template<class F, class... Args>
decltype(auto) ThreadPool::commitTask(F&& f, Args&&... args)
{
//using return_type = std::invoke_result_t<F, Args...>; C++17
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("commitTask on stopped ThreadPool");
if (tasks.size() >= maxQueueSize)
tasks_full_flag.wait(lock, [&]() {return tasks.size() < maxQueueSize; });
tasks.emplace([task]() { (*task)(); });
}
condition.notify_one();
return res;
}
// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
stop = true;
condition.notify_all();
for (auto& worker : workers)
worker.join();
}
inline void ThreadPool::newWorker()
{
auto& flag = tasks_full_flag;
workers.emplace_back([this, &flag] {
while (true)
{
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock, [&] { return this->stop || !this->tasks.empty(); });
if (this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front());
this->tasks.pop();
flag.notify_one();
}
task(); //单个线程的耗时任务
}
}
);
}
#endif