基于C++11标准库的动态线程池,支持随时调整大小、暂停、重启、关闭等操作,支持等待任务执行结果

1 篇文章 0 订阅
1 篇文章 0 订阅

作者申明:

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

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dqsjqian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值