线程池类
线程池是一种池化资源技术,也是一种多线程模式。线程池调度线程执行任务,通常序列化任务,形成任务队列。
设计原理
调度设计
调度的关键在于条件变量。当不满足执行条件时,阻塞线程以让出处理器,在满足条件之前,不占用处理器,以提高资源利用率。
- 当任务队列为空时,阻塞守护线程。一旦新添任务,立即激活守护线程,由守护线程通知工作线程获取任务。
- 当无闲置工作线程时,阻塞守护线程。一旦新增闲置工作线程,激活守护线程。倘若任务队列为空,再次阻塞守护线程,否则通知闲置工作线程获取任务。
- 工作线程在完成任务之后,主动从任务队列获取任务。若任务队列为空,则增加闲置工作线程数量,并且阻塞工作线程。
- 当销毁线程池时,等待守护线程退出。而守护线程在退出之前,等待所有工作线程退出。
- 工作线程在退出之前,默认执行任务队列的所有任务。可选取出所有任务或者清空任务队列,以支持工作线程立即退出。
增删线程策略
线程池提供设置容量方法,由守护线程异步增删工作线程。当任务队列非空时,一次性增加工作线程;当存在闲置工作线程时,逐个删减工作线程。
文件依赖性
线程池类ThreadPool定义于头文件ThreadPool.h,实现于源文件ThreadPool.cpp。
ThreadPool.h
- GitCode: ThreadPool.h
- Gitee: ThreadPool.h
- GitHub: ThreadPool.h
ThreadPool.cpp
- GitCode: ThreadPool.cpp
- Gitee: ThreadPool.cpp
- GitHub: ThreadPool.cpp
线程池类ThreadPool组合线程表和任务队列。线程表元素为线程类Thread,定义于头文件Thread.h,而任务队列采用双缓冲队列类模板DoubleQueue,定义于头文件DoubleQueue.hpp。
Thread.h
DoubleQueue.hpp
- GitCode: DoubleQueue.hpp
- Gitee: DoubleQueue.hpp
- GitHub: DoubleQueue.hpp
线程类Thread的设计与实现见文章——线程类。
双缓冲队列类模板DoubleQueue的设计与实现见文章——双缓冲队列模板。
守护线程的阻塞与激活依赖于强化条件类模板Condition。强化条件类模板Condition用于精准控制阻塞与激活,当激活先于阻塞时,确保线程正常退出,其定义于头文件Condition.hpp。
Condition.hpp
- GitCode: Condition.hpp
- Gitee: Condition.hpp
- GitHub: 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,避免造成名称污染。
结构体的成员变量如表所示:
变量名 | 类型 | 说明 |
---|---|---|
_valid | std::atomic_bool | 线程有效性 |
_condition | Condition<> | 强化条件变量 |
_thread | std::thread | 守护线程 |
_threadTable | std::list<Thread> | 线程表 |
_capacity | std::atomic<SizeType> | 线程池容量 |
_totalSize | std::atomic<SizeType> | 总线程数量 |
_idleSize | std::atomic<SizeType> | 闲置线程数量 |
_taskQueue | std::shared_ptr<QueueType> | 任务队列 |
_callback | Callback | 回调函数子 |
成员函数
数据结构体
过滤无效任务
从任务队列删除无效任务,计算并返回有效任务数量。
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行:定义条件变量的谓词,作为不必等待通知的条件。以下条件之一成立,则守护线程不必等待:
- 在守护线程有效的情况下:
a. 任务队列非空并且存在闲置线程。
b. 任务队列非空并且需要增加线程。
c. 存在闲置线程并且需要删减线程。 - 在守护线程无效的情况下:
a. 任务队列非空并且存在闲置线程。
b. 所有线程闲置。
- 在守护线程有效的情况下:
- 第28,55行:若条件变量的谓词非真,则自动解锁互斥元,阻塞守护线程,直至通知激活,再次锁定互斥元。
- 第30,31行:定义守护线程退出条件。以下所有条件成立,则守护线程退出:
- 守护线程无效。
- 任务队列为空。
- 所有线程闲置。
- 第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();
}