《ZLToolKit源码学习笔记》(10)线程模块之线程池

 系列文章目录

《ZLToolKit源码学习笔记》(1)VS2019源码编译

《ZLToolKit源码学习笔记》(2)工具模块之日志功能分析

《ZLToolKit源码学习笔记》(3)工具模块之终端命令解析

《ZLToolKit源码学习笔记》(4)工具模块之消息广播器

《ZLToolKit源码学习笔记》(5)工具模块之资源池

《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.1、start-创建并启动线程

1.2、wait-等待线程组中所有线程退出

1.3、shutdown-队列中所有任务执行完成后让线程退出

1.4、async-异步执行任务

1.5、async-first异步优先执行任务

 二、任务类型

2.1、TaskIn

2.2、Task::Ptr

三、使用


一、线程池

 前面已经写过线程池的类图关系,这里再看下:

 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);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Libevent是一个事件驱动的网络编程框架,而event.h是其核心头文件之一。该头文件定义了事件处理相关的结构体、函数和宏等内容。 下面是event.h中常用的一些定义和函数: ### 1.事件回调函数 ```c typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg); ``` 该类型定义了事件回调函数的原型,其中fd是事件所在的文件描述符,events是事件类型,arg是用户传入的参数。 ### 2.事件结构体 ```c struct event { event_callback_fn ev_callback; // 事件回调函数 int ev_fd; // 事件所在的文件描述符 short ev_events; // 事件类型 short ev_res; // 事件结果 struct event_base *ev_base; // 事件所属的event_base void *ev_arg; // 用户传入的参数 }; ``` 该结构体表示一个事件,其中ev_callback是事件回调函数,ev_fd是事件所在的文件描述符,ev_events是事件类型,ev_res是事件结果,ev_base是事件所属的event_base,ev_arg是用户传入的参数。 ### 3.事件类型 ```c #define EV_TIMEOUT 0x01 #define EV_READ 0x02 #define EV_WRITE 0x04 #define EV_SIGNAL 0x08 #define EV_PERSIST 0x10 #define EV_ET 0x20 ``` 该宏定义了事件类型,分别为超时事件、读事件、写事件、信号事件、持续事件和边缘触发事件。 ### 4.事件处理函数 ```c struct event_base *event_base_new(void); int event_base_dispatch(struct event_base *base); int event_base_loopexit(struct event_base *base, const struct timeval *tv); void event_base_free(struct event_base *base); ``` 这些函数用于创建event_base、处理事件、退出事件循环和释放event_base等操作。 以上是event.h中的一些常用内容,更多细节可以查看源码和官方文档。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

秦时小

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

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

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

打赏作者

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

抵扣说明:

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

余额充值