系列文章目录
《ZLToolKit源码学习笔记》(1)VS2019源码编译
《ZLToolKit源码学习笔记》(2)工具模块之日志功能分析
《ZLToolKit源码学习笔记》(3)工具模块之终端命令解析
《ZLToolKit源码学习笔记》(4)工具模块之消息广播器
《ZLToolKit源码学习笔记》(6)线程模块之整体框架概述
《ZLToolKit源码学习笔记》(7)线程模块之线程池组件:任务队列与线程组
《ZLToolKit源码学习笔记》(8)线程模块之线程负载计算器
《ZLToolKit源码学习笔记》(9)线程模块之任务执行器
《ZLToolKit源码学习笔记》(10)线程模块之线程池(本文)
《ZLToolKit源码学习笔记》(11)线程模块之工作线程池WorkThreadPool
《ZLToolKit源码学习笔记》(12)事件轮询模块之整体框架概述
《ZLToolKit源码学习笔记》(13)事件轮询模块之管道的简单封装
《ZLToolKit源码学习笔记》(14)事件轮询模块之定时器
《ZLToolKit源码学习笔记》(15)事件轮询模块之事件轮询器EventPoller
《ZLToolKit源码学习笔记》(16)网络模块之整体框架概述
《ZLToolKit源码学习笔记》(17)网络模块之基础接口封装类SockUtil
《ZLToolKit源码学习笔记》(18)网络模块之Buffer缓存
《ZLToolKit源码学习笔记》(19)网络模块之套接字封装
《ZLToolKit源码学习笔记》(20)网络模块之TcpServer
《ZLToolKit源码学习笔记》(21)网络模块之TcpClient与Session
《ZLToolKit源码学习笔记》(22)网络模块之UdpServer
前言
基于任务队列(TaskQueue)和线程组(thread_group),实现线程池,线程池中所有线程等待在任务队列上,循环执行:等待任务到来->获取到任务->执行任务。
目录
1.3、shutdown-队列中所有任务执行完成后让线程退出
一、线程池
前面已经写过线程池的类图关系,这里再看下:
ThreadPool继承自TaskExecutor,TaskExecutor又同时继承了ThreadLoadCounter和TaskExecutorInterface,所以线程池支持负载统计、同步以及异步执行任务。以下是线程池支持的所有接口说明。
1.1、start-创建并启动线程
线程组中创建指定数量的线程,当线程组中线程数不足时,补齐缺少的。已经达到指定数量时,该接口什么也不做。
void start() {
if (_thread_num <= 0) {
return;
}
size_t total = _thread_num - _thread_group.size();
for (size_t i = 0; i < total; ++i) {
_thread_group.create_thread(bind(&ThreadPool::run, this));
}
}
1.2、wait-等待线程组中所有线程退出
void wait() {
_thread_group.join_all();
}
1.3、shutdown-队列中所有任务执行完成后让线程退出
void shutdown() {
_queue.push_exit(_thread_num);
}
队列的push_task和push_task_first会插入任务并触发一次唤醒wait的操作,等待在队列上的线程此时会从队列中获取到任务并执行。
但是push_exit没有给队列插入任务,也会触发其参数指定的n次唤醒wait的操作,等待在队列上的线程被唤醒后,因为实际并没有插入任务,所以这n次是获取不到任务的,队列为空。
基于上述条件,实现了run函数的自动退出(获取任务失败时,退出线程),push_exit指定多唤醒的次数与线程组中线程数量一致,每唤醒一次,就有一个线程被退出,最终刚好让所有线程都退出。
调用shutdown之后,需要调用wait()函数等待线程全部退出。
void run() {
ThreadPool::setPriority(_priority);
Task::Ptr task;
while (true) {
startSleep();
if (!_queue.get_task(task)) {
//空任务,退出线程
break;
}
sleepWakeUp();
try {
(*task)();
task = nullptr;
} catch (std::exception &ex) {
ErrorL << "ThreadPool执行任务捕获到异常:" << ex.what();
}
}
}
1.4、async-异步执行任务
如果允许同步执行,并且当前调用线程在线程组中,则同步执行任务。否则将任务放入队列中异步执行。
Task::Ptr async(TaskIn task,bool may_sync = true) override {
if (may_sync && _thread_group.is_this_thread_in()) {
task();
return nullptr;
}
auto ret = std::make_shared<Task>(std::move(task));
_queue.push_task(ret);
return ret;
}
1.5、async-first异步优先执行任务
与async的区别是,async将任务放在队尾,async-first将任务放在队首,确保优先执行。
Task::Ptr async_first(TaskIn task,bool may_sync = true) override{
if (may_sync && _thread_group.is_this_thread_in()) {
task();
return nullptr;
}
auto ret = std::make_shared<Task>(std::move(task));
_queue.push_task_first(ret);
return ret;
}
二、任务类型
这部分看一下任务队列中插入的任务类型。
TaskQueue<Task::Ptr> _queue;
队列中的任务类型是Task::Ptr,此外,我们还需要看另一个类型,TaskIn,他作为用户添加任务时的任务类型。在TaskExecutor.h中,两者声明如下:
using TaskIn = function<void()>;
using Task = TaskCancelableImp<void()>;
using Ptr = std::shared_ptr<TaskCancelableImp>;
2.1、TaskIn
一个基于std::function的可调用对象封装类型,其返回值是void,且没有参数。用户可以传入任何符合要求的可调用对象,如lambda表达式、函数指针、重载了operator()的类函数对象。
2.2、Task::Ptr
TaskCancelableImp<void()>类型的智能指针。 TaskCancelableImp是一个可取消的任务封装器,实际上是对TaskIn的再次封装。TaskCancelableImp的内部存储了TaskIn类型的对象。
看一下TaskCancelableImp的模板实现:
template<class R, class... ArgTypes>
class TaskCancelableImp;
template<class R, class... ArgTypes>
class TaskCancelableImp<R(ArgTypes...)> : public TaskCancelable {
public:
using Ptr = std::shared_ptr<TaskCancelableImp>;
using func_type = function<R(ArgTypes...)>;
~TaskCancelableImp() = default;
template<typename FUNC>
TaskCancelableImp(FUNC &&task) {
_strongTask = std::make_shared<func_type>(std::forward<FUNC>(task));
_weakTask = _strongTask;
}
void cancel() override {
_strongTask = nullptr;
}
operator bool() {
return _strongTask && *_strongTask;
}
void operator=(nullptr_t) {
_strongTask = nullptr;
}
R operator()(ArgTypes ...args) const {
auto strongTask = _weakTask.lock();
if (strongTask && *strongTask) {
return (*strongTask)(forward<ArgTypes>(args)...);
}
return defaultValue<R>();
}
protected:
std::weak_ptr<func_type> _weakTask;
std::shared_ptr<func_type> _strongTask;
};
class TaskCancelableImp<R(ArgTypes...)>是TaskCancelableImp的偏例化(部分特例化),它的全特化版本就是Task(using Task = TaskCancelableImp<void()>)。
构造函数TaskCancelableImp(FUNC &&task) 中可以看出,它接受的是一个func_type类型的参数,即function<R(ArgTypes...)>,这与我们让用户输入的TaskIn的类型一致。该类提供了cancel接口,可以在任务执行前取消任务。
为什么任务队列不直接使用TaskIn类型而要用Task类型呢?主要是为了支持任务的可中途取消。
TaskQueue<std::shared_ptr<TaskIn>> _queue;
TaskQueue<std::shared_ptr<Task>> _queue;
三、使用
线程池的测试程序是test_threadPoolBenchmark.cpp文件,具体可以看源码。
该部分使用了lambda表达式作为任务:
pool.async([&](){
if(++count >= 1000*10000){
InfoL << "执行1000万任务总共耗时:" << ticker.elapsedTime() << "ms";
}
});
当然也可以使用函数指针或者函数对象,以重载operator()的函数对象作为任务的代码如下:
class MyTask {
public:
MyTask(atomic_llong* pCount) :m_pCount(pCount){
}
void operator()() {
if (++(*m_pCount) >= 1000 * 10000) {
InfoL << "执行1000万任务总共耗时:" << ticker.elapsedTime() << "ms";
}
}
private:
atomic_llong* m_pCount;
};
MyTask myTask(&count);
pool.async(myTask);