C++17 线程池类


线程池是一种池化资源技术,也是一种多线程模式。线程池调度线程执行任务,通常序列化任务,形成任务队列。

设计原理

调度设计

调度的关键在于条件变量。当不满足执行条件时,阻塞线程以让出处理器,在满足条件之前,不占用处理器,以提高资源利用率。

  1. 当任务队列为空时,阻塞守护线程。一旦新添任务,立即激活守护线程,由守护线程通知工作线程获取任务。
  2. 当无闲置工作线程时,阻塞守护线程。一旦新增闲置工作线程,激活守护线程。倘若任务队列为空,再次阻塞守护线程,否则通知闲置工作线程获取任务。
  3. 工作线程在完成任务之后,主动从任务队列获取任务。若任务队列为空,则增加闲置工作线程数量,并且阻塞工作线程。
  4. 当销毁线程池时,等待守护线程退出。而守护线程在退出之前,等待所有工作线程退出。
  5. 工作线程在退出之前,默认执行任务队列的所有任务。可选取出所有任务或者清空任务队列,以支持工作线程立即退出。

增删线程策略

线程池提供设置容量方法,由守护线程异步增删工作线程。当任务队列非空时,一次性增加工作线程;当存在闲置工作线程时,逐个删减工作线程。

文件依赖性

线程池类ThreadPool定义于头文件ThreadPool.h,实现于源文件ThreadPool.cpp。

ThreadPool.h

ThreadPool.cpp

线程池类ThreadPool组合线程表和任务队列。线程表元素为线程类Thread,定义于头文件Thread.h,而任务队列采用双缓冲队列类模板DoubleQueue,定义于头文件DoubleQueue.hpp。

Thread.h

DoubleQueue.hpp

线程类Thread的设计与实现见文章——线程类
双缓冲队列类模板DoubleQueue的设计与实现见文章——双缓冲队列模板

守护线程的阻塞与激活依赖于强化条件类模板Condition。强化条件类模板Condition用于精准控制阻塞与激活,当激活先于阻塞时,确保线程正常退出,其定义于头文件Condition.hpp。

Condition.hpp

强化条件类模板Condition的设计与实现见文章——强化条件变量
关于激活先于阻塞,更多介绍见章节——程序设计技巧之激活先于阻塞。

类定义

线程池类ThreadPool与代理类Proxy定义于头文件ThreadPool.h,代码如下所示:

#pragma once

#include <functional>
#include <utility>
#include <memory>
#include <list>
#include <mutex>

class ThreadPool final
{
	struct Structure;

public:
	class Proxy;

private:
	using DataType = std::shared_ptr<Structure>;

public:
	using TaskType = std::function<void()>;
	using TaskQueue = std::list<TaskType>;
	using SizeType = TaskQueue::size_type;

private:
	mutable std::mutex _mutex;
	DataType _data;

private:
	static DataType move(ThreadPool& _left, \
		ThreadPool&& _right);

	static void create(DataType&& _data, \
		SizeType _capacity);

	static void destroy(DataType&& _data);

	static SizeType adjust(DataType& _data);

	static void execute(DataType _data);

public:
	static SizeType getConcurrency() noexcept;

private:
	auto load() const
	{
		std::lock_guard lock(_mutex);
		return _data;
	}

public:
	ThreadPool(SizeType _capacity = getConcurrency());

	ThreadPool(const ThreadPool&) = delete;

	ThreadPool(ThreadPool&& _another) noexcept;

	~ThreadPool() noexcept;

	ThreadPool& operator=(const ThreadPool&) = delete;

	ThreadPool& operator=(ThreadPool&& _threadPool) noexcept;

	SizeType getCapacity() const;

	bool setCapacity(SizeType _capacity);

	SizeType getTotalSize() const;

	SizeType getIdleSize() const;

	SizeType getTaskSize() const;

	bool pushTask(const TaskType& _task);
	bool pushTask(TaskType&& _task);

	template <typename _Functor>
	bool pushTask(const _Functor& _functor)
	{
		return pushTask(TaskType(_functor));
	}
	template <typename _Functor>
	bool pushTask(_Functor&& _functor)
	{
		return pushTask(TaskType(std::forward<_Functor>(_functor)));
	}
	template <typename _Functor, typename... _Args>
	bool pushTask(_Functor&& _functor, _Args&&... _args);

	bool pushTask(TaskQueue& _taskQueue);
	bool pushTask(TaskQueue&& _taskQueue);

	bool popTask(TaskQueue& _taskQueue);

	void clearTask();

	Proxy getProxy() const;
};

class ThreadPool::Proxy final
{
	DataType _data;

public:
	Proxy(const decltype(_data)& _data) noexcept : \
		_data(_data) {}

	explicit operator bool() const noexcept { return valid(); }

	bool valid() const noexcept
	{
		return static_cast<bool>(_data);
	}

	SizeType getCapacity() const noexcept;

	bool setCapacity(SizeType _capacity);

	SizeType getTotalSize() const noexcept;

	SizeType getIdleSize() const noexcept;

	SizeType getTaskSize() const noexcept;

	bool pushTask(const TaskType& _task);
	bool pushTask(TaskType&& _task);

	template <typename _Functor>
	bool pushTask(const _Functor& _functor)
	{
		return pushTask(TaskType(_functor));
	}
	template <typename _Functor>
	bool pushTask(_Functor&& _functor)
	{
		return pushTask(TaskType(std::forward<_Functor>(_functor)));
	}
	template <typename _Functor, typename... _Args>
	bool pushTask(_Functor&& _functor, _Args&&... _args);

	bool pushTask(TaskQueue& _taskQueue);
	bool pushTask(TaskQueue&& _taskQueue);

	bool popTask(TaskQueue& _taskQueue);

	void clearTask();
};

template <typename _Functor, typename... _Args>
bool ThreadPool::Proxy::pushTask(_Functor&& _functor, _Args&&... _args)
{
	auto functor = std::bind(std::forward<_Functor>(_functor), \
		std::forward<_Args>(_args)...);
	return pushTask(TaskType(functor));
}

template <typename _Functor, typename... _Args>
bool ThreadPool::pushTask(_Functor&& _functor, _Args&&... _args)
{
	auto functor = std::bind(std::forward<_Functor>(_functor), \
		std::forward<_Args>(_args)...);
	return pushTask(TaskType(functor));
}

代码解析

  • 第77-88,128-139行:适配不同任务接口,推进线程池模板化。
  • 第152,153,160,161行:绑定任务参数,构建任务函数子。

线程池类不支持复制语义,支持移动语义,其实例自身和接口都具备线程安全性;而代理类同时支持复制语义和移动语义,其实例自身不具备线程安全性,除赋值之外的接口具有线程安全性。

代理类与线程池类具有相同功能。相比于线程池类,代理类提供轻量接口,以减少原子操作。

成员变量

线程池类采用实现细节隐藏技巧,仅有互斥元_mutex和共享指针_data两个私有成员变量。以共享指针std::shared_ptr,指向结构体Structure实例,实现自动管理内存,支持ThreadPool与std::thread共享数据,在ThreadPool实例析构之时,或者当std::thread执行结束时,二者任一时机释放内存。

代理类同样采用实现细节隐藏技巧,仅以共享指针_data作为唯一私有成员变量,支持多个代理类实例共享相同结构体Structure实例。

为了隐藏类的属性与行为,成员变量可选指针或者引用。
关于实现细节隐藏技巧,更多介绍见章节——编译依存性之实现细节隐藏技巧。

成员函数

数据共享
守护线程主函数与调整线程数量函数声明为静态成员,除去与类成员指针this的关联性,以支持移动语义构造和赋值类实例。同时降低ThreadPool与std::thread的耦合性,并且二者共享成员结构体数据。

封装性
根据访问情景,限定仅由类成员函数调用的成员函数为私有成员。线程池类删除默认复制构造函数和默认复制赋值运算符函数,实现默认移动构造函数和默认移动赋值运算符函数。

线程安全性
线程池类接口通过互斥元构建临界区,以原子操作获取共享指针,而守护线程主函数亦持有共享指针,既确保接口的线程安全性,又不影响守护线程性能。

泛化
添加任务函数pushTask结合成员函数模板与模板形参包,适配不同形式的可调用任务,从而泛化线程池类与代理类接口。

类实现

数据结构

结构体Structure定义于源文件ThreadPool.cpp,代码如下所示:

#include "ThreadPool.h"
#include "Condition.hpp"
#include "DoubleQueue.hpp"
#include "Thread.h"

#include <cstdint>
#include <exception>
#include <atomic>
#include <thread>

#define SET_ATOMIC(SizeType, Arithmetic, functor, field) \
SizeType functor(SizeType _size, Arithmetic _arithmetic) noexcept \
{ \
	constexpr auto MEMORY_ORDER = std::memory_order_relaxed; \
	switch (_arithmetic) \
	{ \
	case Arithmetic::REPLACE: \
		return field.exchange(_size, MEMORY_ORDER); \
	case Arithmetic::INCREASE: \
		return field.fetch_add(_size, MEMORY_ORDER); \
	case Arithmetic::DECREASE: \
		return field.fetch_sub(_size, MEMORY_ORDER); \
	default: \
		return field.load(MEMORY_ORDER); \
	} \
}

struct ThreadPool::Structure
{
	enum class Arithmetic : std::uint8_t
	{
		REPLACE,
		INCREASE,
		DECREASE
	};

	using QueueType = DoubleQueue<TaskType>;
	using Callback = Thread::Callback;

	std::atomic_bool _valid;
	Condition<> _condition;
	std::thread _thread;
	std::list<Thread> _threadTable;

	std::atomic<SizeType> _capacity;
	std::atomic<SizeType> _totalSize;
	std::atomic<SizeType> _idleSize;

	std::shared_ptr<QueueType> _taskQueue;
	Callback _callback;

	template <typename _TaskQueue>
	static auto filterTask(_TaskQueue& _taskQueue);

	Structure() : \
		_taskQueue(std::make_shared<QueueType>()) {}

	bool isValid() const noexcept
	{
		return _valid.load(std::memory_order_relaxed);
	}

	void setValid(bool _valid) noexcept
	{
		this->_valid.store(_valid, \
			std::memory_order_relaxed);
	}

	auto getCapacity() const noexcept
	{
		return _capacity.load(std::memory_order_relaxed);
	}

	void setCapacity(SizeType _capacity, bool _notified = false);

	auto getTotalSize() const noexcept
	{
		return _totalSize.load(std::memory_order_relaxed);
	}

	SET_ATOMIC(SizeType, Arithmetic, setTotalSize, _totalSize);

	auto getIdleSize() const noexcept
	{
		return _idleSize.load(std::memory_order_relaxed);
	}

	SET_ATOMIC(SizeType, Arithmetic, setIdleSize, _idleSize);

	bool pushTask(const TaskType& _task);
	bool pushTask(TaskType&& _task);

	bool pushTask(TaskQueue& _taskQueue);
	bool pushTask(TaskQueue&& _taskQueue);
};

#undef SET_ATOMIC

代码解析

  • 第11-26行:定义宏SET_ATOMIC,用以自动生成设置原子变量的成员函数代码。
  • 第30-35行:定义强枚举Arithmetic,列举原子算术类型,支持置换、加法、减法运算。
  • 第55,56行:在构造函数体之外,运用初始化表构造任务队列。若先以运算符new创建实例,再交由共享指针std::shared_ptr托管,则至少二次分配内存,先为实例分配内存,再为共享指针的控制块分配内存。而std::make_shared典型地仅分配一次内存,实例内存和控制块内存连续。
  • 第97行:取消定义宏SET_ATOMIC,避免造成名称污染。

结构体的成员变量如表所示:

变量名类型说明
_validstd::atomic_bool线程有效性
_conditionCondition<>强化条件变量
_threadstd::thread守护线程
_threadTablestd::list<Thread>线程表
_capacitystd::atomic<SizeType>线程池容量
_totalSizestd::atomic<SizeType>总线程数量
_idleSizestd::atomic<SizeType>闲置线程数量
_taskQueuestd::shared_ptr<QueueType>任务队列
_callbackCallback回调函数子

成员函数

数据结构体

过滤无效任务

从任务队列删除无效任务,计算并返回有效任务数量。

template <typename _TaskQueue>
auto ThreadPool::Structure::filterTask(_TaskQueue& _taskQueue)
{
	decltype(_taskQueue.size()) size = 0;
	for (auto iterator = _taskQueue.cbegin(); \
		iterator != _taskQueue.cend();)
		if (!*iterator)
			iterator = _taskQueue.erase(iterator);
		else
		{
			++iterator;
			++size;
		}
	return size;
}

代码解析

  • 第7,8行:从任务队列删除无效任务,避免放入无效任务而导致线程泄漏。

关于线程泄漏,更多介绍见章节——程序设计技巧之线程泄漏。

设置线程池容量

指定新容量与通知标记,先设置新容量,并获取旧容量,再根据通知标记与新旧容量,决定是否激活守护线程。

void ThreadPool::Structure::setCapacity(SizeType _capacity, \
	bool _notified)
{
	auto capacity = this->_capacity.exchange(_capacity, \
		std::memory_order_relaxed);
	if (_notified && capacity != _capacity)
		_condition.notify_one(Condition<>::Policy::RELAXED);
}

代码解析

  • 第6,7行:当需要通知守护线程,并且新容量与旧容量不同时,激活或许阻塞的守护线程。
放入任务

可选复制语义和移动语义,向任务队列放入任务,返回放入任务成功与否。

bool ThreadPool::Structure::pushTask(const TaskType& _task)
{
	auto result = _taskQueue->push(_task);
	if (result && result.value() == 0)
		_condition.notify_one(Condition<>::Policy::RELAXED);
	return result.has_value();
}

bool ThreadPool::Structure::pushTask(TaskType&& _task)
{
	auto result = _taskQueue->push(std::forward<TaskType>(_task));
	if (result && result.value() == 0)
		_condition.notify_one(Condition<>::Policy::RELAXED);
	return result.has_value();
}

代码解析

  • 第4,5,12,13行:倘若放入任务成功,而在放入任务之前,任务队列为空队列,守护线程或许处于阻塞状态,通过条件变量激活之。
批量放入任务

可选复制语义和移动语义,向任务队列放入任务。由于涉及锁定和解锁互斥元,因此对于大量突发性任务,放入单任务方式频繁访问互斥元,将会降低执行效率。而批量放入任务方式形成缓冲区,避免频繁操作互斥元。

bool ThreadPool::Structure::pushTask(TaskQueue& _taskQueue)
{
	if (filterTask(_taskQueue) <= 0) return false;

	auto result = this->_taskQueue->push(_taskQueue);
	if (result && result.value() == 0)
		_condition.notify_one(Condition<>::Policy::RELAXED);
	return result.has_value();
}

bool ThreadPool::Structure::pushTask(TaskQueue&& _taskQueue)
{
	if (filterTask(_taskQueue) <= 0) return false;

	auto result = this->_taskQueue->push(std::forward<TaskQueue>(_taskQueue));
	if (result && result.value() == 0)
		_condition.notify_one(Condition<>::Policy::RELAXED);
	return result.has_value();
}

代码解析

  • 第3,13行:在过滤无效任务之后,若不存在有效任务,则批量放入任务失败。
  • 第6,7,16,17行:倘若放入任务成功,而在放入任务之前,任务队列为空队列,守护线程或许处于阻塞状态,通过条件变量激活之。返回添加任务成功与否。

批量放入任务也存在缺陷。当任务队列为空队列时,若继续缓存任务,则线程池闲置,而无法及时执行任务,导致未充分利用资源。

代理类

获取线程池容量

由于线程池容量可以改变,因此取得容量只是一时刻的参考数据。

auto ThreadPool::Proxy::getCapacity() const noexcept \
-> SizeType
{
	return _data ? _data->getCapacity() : 0;
}

代码解析

  • 第4行:倘若共享指针有效,返回取得的线程池容量,否则返回零,同时表明获取失败。
设置线程池容量

通过改变线程池容量,异步增删线程数量。

bool ThreadPool::Proxy::setCapacity(SizeType _capacity)
{
	if (_capacity <= 0 || !_data) return false;

	_data->setCapacity(_capacity, true);
	return true;
}

代码解析

  • 第3行:若新容量非法,或者共享指针无效,则设置线程池容量失败。
  • 第5行:设置线程池容量,并尝试通知守护线程。
获取总线程数量

由于总线程数量可以改变,因此取得数量只是一时刻的参考数据。

auto ThreadPool::Proxy::getTotalSize() const noexcept \
-> SizeType
{
	return _data ? _data->getTotalSize() : 0;
}
获取闲置线程数量

由于闲置线程数量实时变化,因此取得数量只是一时刻的参考数据。

auto ThreadPool::Proxy::getIdleSize() const noexcept \
-> SizeType
{
	return _data ? _data->getIdleSize() : 0;
}
获取任务数量

由于任务数量实时变化,因此取得数量只是一时刻的参考数据。

auto ThreadPool::Proxy::getTaskSize() const noexcept \
-> SizeType
{
	return _data ? _data->_taskQueue->size() : 0;
}
放入任务

向任务队列放入任务,可选复制语义和移动语义。

bool ThreadPool::Proxy::pushTask(const TaskType& _task)
{
	return _task && _data && _data->pushTask(_task);
}

bool ThreadPool::Proxy::pushTask(TaskType&& _task)
{
	return _task && _data \
		&& _data->pushTask(std::forward<TaskType>(_task));
}

代码解析

  • 第3,8,9行:检查任务有效性,避免放入无效任务,从而导致线程泄漏。
批量放入任务

向任务队列批量放入任务,支持移交全部元素,也支持移动语义。

bool ThreadPool::Proxy::pushTask(TaskQueue& _taskQueue)
{
	return _data && _data->pushTask(_taskQueue);
}

bool ThreadPool::Proxy::pushTask(TaskQueue&& _taskQueue)
{
	return _data \
		&& _data->pushTask(std::forward<TaskQueue>(_taskQueue));
}
批量取出任务

从任务队列取出所有任务。

bool ThreadPool::Proxy::popTask(TaskQueue& _taskQueue)
{
	return _data && _data->_taskQueue->pop(_taskQueue);
}
清空任务

若共享指针非空,则清空任务队列。

void ThreadPool::Proxy::clearTask()
{
	if (_data)
		_data->_taskQueue->clear();
}

线程池类

移动数据

实现移动语义赋值,并确保操作原子性。

auto ThreadPool::move(ThreadPool& _left, \
	ThreadPool&& _right) -> DataType
{
	std::lock_guard leftLock(_left._mutex);
	auto data = std::move(_left._data);

	std::lock_guard rightLock(_right._mutex);
	_left._data = std::move(_right._data);
	return data;
}

代码解析

  • 第4,7行:分别锁定两个互斥元,以减小互斥粒度。
  • 第5,9行:获取并返回原始数据。
创建线程池

初始化回调函数子、线程表和守护线程。

void ThreadPool::create(DataType&& _data, SizeType _capacity)
{
	using Arithmetic = Structure::Arithmetic;

	_data->_callback = [_data = std::weak_ptr(_data)](Thread::ThreadID _id, bool _idle)
	{
		if (!_idle) return;

		if (auto data = _data.lock(); data \
			&& (data->setIdleSize(1, Arithmetic::INCREASE) == 0 \
			|| data->getIdleSize() >= data->getTotalSize()))
			data->_condition.notify_one(Condition<>::Policy::RELAXED);
	};

	_capacity = _capacity > 0 ? _capacity : 1;
	for (decltype(_capacity) index = 0; index < _capacity; ++index)
	{
		Thread thread;
		thread.configure(_data->_taskQueue, _data->_callback);
		_data->_threadTable.push_back(std::move(thread));
	}

	_data->setCapacity(_capacity);
	_data->setTotalSize(_capacity, Arithmetic::REPLACE);
	_data->setIdleSize(_capacity, Arithmetic::REPLACE);

	_data->setValid(true);

	_data->_thread = std::thread(execute, _data);
}

代码解析

  • 第5-13行:定义Lambda表达式,作为回调函数子。在线程从任务队列获取任务之后,回调通知守护线程。如果获取任务成功就忽略通知,否则增加闲置线程数量。倘若在增加闲置线程数量之前,无闲置线程,或者在增加闲置线程数量之后,所有线程闲置,守护线程或许处于阻塞状态,通过条件变量激活之。

在Lambda表达式中,通过智能指针std::weak_ptr,解决智能指针std::shared_ptr的循环引用问题,防止销毁线程池出现资源泄漏。
智能指针std::shared_ptr的循环引用问题见文章——智能指针std::shared_ptr之循环引用

  • 第15-21行:根据线程池容量创建线程,确保至少创建一个线程。为线程配置任务队列和回调函数子,并且放入线程表。
  • 第23-25行:设置线程池容量、总线程数量和闲置线程数量。
  • 第27行:守护线程状态设为有效。
  • 第29行:创建std::thread实例,即守护线程,以共享指针_data为参数,执行函数execute。
销毁线程池

通知并且等待守护线程退出。

void ThreadPool::destroy(DataType&& _data)
{
	using Arithmetic = Structure::Arithmetic;
	using Policy = Condition<>::Policy;

	if (!_data->isValid()) return;

	_data->setValid(false);
	_data->_condition.notify_all(Policy::RELAXED);

	if (_data->_thread.joinable())
		_data->_thread.join();

	_data->setCapacity(0);
	_data->setTotalSize(0, Arithmetic::REPLACE);
	_data->setIdleSize(0, Arithmetic::REPLACE);
}

代码解析

  • 第6行:倘若守护线程无效,不必再销毁线程池,直接退出函数。
  • 第8,9行:守护线程状态设为无效,并且通知守护线程退出。
  • 第11,12行:挂起直到守护线程退出为止。
  • 第14-16行:重置线程池容量、总线程数量和闲置线程数量。
调整线程数量

若增加线程数量,则同步创建线程;否则异步销毁线程,返回待减少的线程数量,在线程闲置之时销毁线程。

auto ThreadPool::adjust(DataType& _data) -> SizeType
{
	using Arithmetic = Structure::Arithmetic;

	auto size = _data->getTotalSize();
	auto capacity = _data->getCapacity();

	if (size >= capacity) return size - capacity;

	size = capacity - size;
	for (decltype(size) index = 0; index < size; ++index)
	{
		Thread thread;
		thread.configure(_data->_taskQueue, _data->_callback);
		_data->_threadTable.push_back(std::move(thread));
	}

	_data->setTotalSize(size, Arithmetic::INCREASE);
	_data->setIdleSize(size, Arithmetic::INCREASE);
	return 0;
}

代码解析

  • 第8行:若总线程数量大于等于线程池容量,则返回待减少的线程数量。
  • 第10-16行:当总线程数量小于线程池容量时,立即创建新线程并放入线程表。
  • 第18,19行:增加总线程数量和闲置线程数量。
守护线程主函数

守护线程在创建之后转为阻塞状态,在退出之前清空线程表。当调整线程数量时,立即创建线程,或者延迟销毁线程。当新增任务时,激活闲置线程。若未发生前述事件,则再次转为阻塞状态。

void ThreadPool::execute(DataType _data)
{
	using Arithmetic = Structure::Arithmetic;

	auto predicate = [&_data]
	{
		bool empty = _data->_taskQueue->empty();

		if (_data->isValid())
		{
			bool idle = _data->getIdleSize() > 0;
			auto size = _data->getTotalSize();
			auto capacity = _data->getCapacity();
			return !empty \
				&& (idle || size < capacity) \
				|| idle && size > capacity;
		}
		else
		{
			auto size = _data->getIdleSize();
			auto capacity = _data->getTotalSize();
			bool idle = size > 0;
			return !empty && idle \
				|| size >= capacity;
		}
	};

	_data->_condition.wait(predicate);

	while (_data->isValid() || !_data->_taskQueue->empty() \
		|| _data->getIdleSize() < _data->getTotalSize())
	{
		auto size = adjust(_data);

		for (auto iterator = _data->_threadTable.begin(); \
			iterator != _data->_threadTable.end() \
			&& _data->getIdleSize() > 0;)
		{
			if (auto& thread = *iterator; thread.idle())
			{
				if (thread.notify())
					_data->setIdleSize(1, Arithmetic::DECREASE);
				else if (size > 0)
				{
					iterator = _data->_threadTable.erase(iterator);
					_data->setIdleSize(1, Arithmetic::DECREASE);
					_data->setTotalSize(1, Arithmetic::DECREASE);
					--size;
					continue;
				}
			}
			++iterator;
		}

		_data->_condition.wait(predicate);
	}

	_data->_threadTable.clear();
}

代码解析

  • 第5-13行:定义条件变量的谓词,作为不必等待通知的条件。以下条件之一成立,则守护线程不必等待:
    1. 在守护线程有效的情况下:
      a. 任务队列非空并且存在闲置线程。
      b. 任务队列非空并且需要增加线程。
      c. 存在闲置线程并且需要删减线程。
    2. 在守护线程无效的情况下:
      a. 任务队列非空并且存在闲置线程。
      b. 所有线程闲置。
  • 第28,55行:若条件变量的谓词非真,则自动解锁互斥元,阻塞守护线程,直至通知激活,再次锁定互斥元。
  • 第30,31行:定义守护线程退出条件。以下所有条件成立,则守护线程退出:
    1. 守护线程无效。
    2. 任务队列为空。
    3. 所有线程闲置。
  • 第33行:调整线程数量,获取延迟销毁的线程数量。
  • 第39-51行:倘若线程处于闲置状态,尝试通知线程执行任务。若通知成功则减少闲置线程数量,否则判断是否延迟销毁线程。销毁线程需要减少闲置线程数量和总线程数量。
  • 第58行:在守护线程退出之前,释放占用资源,清空线程表,等待销毁所有线程。
获取并发线程数量

封装为公有静态成员函数,便于获取硬件设备支持的并发线程数量。

auto ThreadPool::getConcurrency() noexcept -> SizeType
{
	auto concurrency = std::thread::hardware_concurrency();
	return concurrency > 0 ? concurrency : 1;
}

代码解析
第3,4行:获取实现支持的并发线程数量,若值不可计算则返回一。

默认构造函数

创建共享数据块,根据线程池容量初始化共享数据块。

ThreadPool::ThreadPool(SizeType _capacity) : \
	_data(std::make_shared<Structure>())
{
	create(load(), _capacity);
}

代码解析

  • 第2行:在构造函数体之外,运用初始化表构造成员变量,一步初始共享指针_data。而在函数体之内,初始化分为两个步骤。先在函数体外构造共享指针_data,再于函数体内创建临时对象对_data赋值。
默认移动构造函数

以互斥锁确保线程安全性,并且转移线程池所有权。

ThreadPool::ThreadPool(ThreadPool&& _another) noexcept
{
	try
	{
		std::lock_guard lock(_another._mutex);
		this->_data = std::move(_another._data);
	}
	catch (std::exception&) {}
}

代码解析

  • 第3,4,7,8行:捕获异常,确保移动构造函数的异常安全性。
默认析构函数

销毁线程池,确保析构函数的异常安全性。

ThreadPool::~ThreadPool() noexcept
{
	try
	{
		if (auto data = load())
			destroy(std::move(data));
	}
	catch (std::exception&) {}
}

代码解析

  • 第5,6行:当数据非空时,自动终止守护线程,销毁线程池,以支持移动语义。
默认移动赋值运算符函数

转移线程池所有权,销毁原始线程池,并确保异常安全性。

auto ThreadPool::operator=(ThreadPool&& _threadPool) noexcept \
-> ThreadPool&
{
	if (&_threadPool != this)
	{
		try
		{
			auto data = move(*this, \
				std::forward<ThreadPool>(_threadPool));

			if (data) destroy(std::move(data));
		}
		catch (std::exception&) {}
	}
	return *this;
}

代码解析

  • 第4行:确保二者为不同实例,避免二者为相同实例,从而导致异常销毁线程池。
  • 第8,9行:移动赋值共享数据,获取原始共享数据。
  • 第11行:销毁原始线程池。
获取线程池容量

由于线程池容量可以改变,因此取得容量只是一时刻的参考数据。

auto ThreadPool::getCapacity() const -> SizeType
{
	auto data = load();
	return data ? data->getCapacity() : 0;
}

代码解析

  • 第3行:原子加载共享指针。
  • 第4行:倘若共享指针有效,返回取得的线程池容量,否则返回零,同时表明获取失败。
设置线程池容量

通过改变线程池容量,异步增删线程数量。

bool ThreadPool::setCapacity(SizeType _capacity)
{
	if (_capacity > 0)
		if (auto data = load())
		{
			data->setCapacity(_capacity, true);
			return true;
		}
	return false;
}

代码解析

  • 第3-8行:若新容量合法,共享指针有效,则设置线程池容量,并尝试通知守护线程。
获取总线程数量

由于总线程数量可以改变,因此取得数量只是一时刻的参考数据。

auto ThreadPool::getTotalSize() const -> SizeType
{
	auto data = load();
	return data ? data->getTotalSize() : 0;
}
获取闲置线程数量

由于闲置线程数量实时变化,因此取得数量只是一时刻的参考数据。

auto ThreadPool::getIdleSize() const -> SizeType
{
	auto data = load();
	return data ? data->getIdleSize() : 0;
}
获取任务数量

由于任务数量实时变化,因此取得数量只是一时刻的参考数据。

auto ThreadPool::getTaskSize() const -> SizeType
{
	auto data = load();
	return data ? data->_taskQueue->size() : 0;
}
放入任务

向任务队列放入任务,可选复制语义和移动语义。

bool ThreadPool::pushTask(const TaskType& _task)
{
	if (!_task) return false;

	auto data = load();
	return data && data->pushTask(_task);
}

bool ThreadPool::pushTask(TaskType&& _task)
{
	if (!_task) return false;

	auto data = load();
	return data \
		&& data->pushTask(std::forward<TaskType>(_task));
}

代码解析

  • 第3,11行:检查任务有效性,避免放入无效任务,从而导致线程泄漏。
批量放入任务

向任务队列批量放入任务,支持移交全部元素,也支持移动语义。

bool ThreadPool::pushTask(TaskQueue& _taskQueue)
{
	auto data = load();
	return data && data->pushTask(_taskQueue);
}

bool ThreadPool::pushTask(TaskQueue&& _taskQueue)
{
	auto data = load();
	return data \
		&& data->pushTask(std::forward<TaskQueue>(_taskQueue));
}
批量取出任务

从任务队列取出所有任务。

bool ThreadPool::popTask(TaskQueue& _taskQueue)
{
	auto data = load();
	return data \
		&& data->_taskQueue->pop(_taskQueue);
}
清空任务

原子加载共享指针,若共享指针非空,则清空任务队列。

void ThreadPool::clearTask()
{
	if (auto data = load())
		data->_taskQueue->clear();
}
获取代理

加载非原子数据,创建代理对象。

auto ThreadPool::getProxy() const \
-> Proxy
{
	return load();
}
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值