一步一步写线程之八线程池的完善之三任务的封装

201 篇文章 27 订阅

一、线程池中的任务

在前面的线程池操作中,任务只是通过std::function来实现。从实际的出发需求来说,基本上一般的线程任务使用其实已经够用。但在实际情况中,可能会遇到一些情况,比如不支持c++11,或者为了某种目的无法使用std::function,那么在这种情况下就需要开发者对任务进行单独的封装。
另外,线程的任务的处理,不是简单的直接对任务暴露给外面即可,更好的方式其实是将任务隐藏起,只对外暴露要使用的相关接口即可。所以在本篇重点是对任务进行一下类似于std::function的封装,同时在外面再增加一层“外覆器”(STL中也有类似的实现)。

二、任务的封装处理

此处把任务的封装分成两块,即任务本身的封装和任务的外覆器封装。而任务本身的封装又分为使用std::function和不使用其进行封装。然后在这个基础,实现整个任务的封装处理。

三、源码

看看源码就明白了,先看一下前面类型擦除的例子:

#include <iostream>
#include <memory>
#include <utility>
#include <queue>

//基础应用
typedef void(*pFtask)();
//普通函数
void WorkTask()
{
    std::cout << "do work,type erase" << std::endl;
}
//仿函数
class WorkTaskFunc
{
public:
    void operator()()const
    {
        std::cout << "functor  ,type erase" << std::endl;
    }
};
//lambda表达式
auto tasklambda = []() {std::cout << "lambda,type erase" << std::endl; };

//任务基础抽象类
class BaseTask
{
public:
    BaseTask() = default;
    virtual ~BaseTask() = default;
public:
    virtual void DoWork() const = 0;
    virtual void operator()()const = 0;
};
//标准任务类
template<typename F>
class TaskImpl :public BaseTask
{
public:
    TaskImpl() = default;
    template<typename T>
    TaskImpl(T&& t) :func_(std::forward<T>(t)) {}
    ~TaskImpl() = default;
public:
    void DoWork()const override
    {
        //WorkTask();
        std::cout << "start task!" << std::endl;
    }
    void operator()()const override
    {
        func_();
    }

public:
    F func_;
};

class TaskWrapper;//前向声明
template <typename F>
using is_ok_wrapper =
std::enable_if_t<!std::is_same_v< std::remove_cvref_t<F>, TaskWrapper >,int>;

//任务封装打包器
class TaskWrapper
{
public:
    TaskWrapper() = default;
    template<typename F, is_ok_wrapper<F> = 0>
    TaskWrapper(F &&f)
    {
        using type_decay = std::decay_t<F>;
        using standType =  TaskImpl<type_decay>;
        //typedef     TaskImpl<F>  standType;
        pTask_ = std::make_unique<standType>(std::forward<type_decay>(f));
    }
    ~TaskWrapper() = default;
public:
    TaskWrapper(TaskWrapper&& other)noexcept :pTask_(std::move(other.pTask_)) {}
    TaskWrapper& operator = (TaskWrapper&& rhs)noexcept
    {
        pTask_ = std::move(rhs.pTask_);
        return *this;
    }
    TaskWrapper(const TaskWrapper&) = delete;
    TaskWrapper& operator=(const TaskWrapper&) = delete;
public:
    void operator()()const
    {
        pTask_->operator()();
        //pTask_->DoWork();
    }
public:
    std::unique_ptr<BaseTask> pTask_;
};

其中任务的封装过程在前面的“类型擦除的应用”及“类型擦除应用的优化”中有过分析,如果有什么不太明白的可以去翻回去看看。但是如果需要扩展一下带变参的呢?只需要增加一个变参处理即可。但模板函数是不能做为虚函数的,所以,此处就不需要继承BaskTask了(当然,手写虚表也是可以的,下面的代码中仍然有继承,目的是为了代码的完整性),可以直接在TaskWrapper中增加一个opreator()的重载即可:

#ifndef __TASKIMPL_H__
#define __TASKIMPL_H__

#include "BaseTask.h"
#include <functional>
#include <iostream>
template <typename F>
class TaskImpl : public BaseTask {

public:
  TaskImpl() = default;
  TaskImpl(F &&f) : func_(std::forward<F>(f)) {}
  ~TaskImpl() = default;

public:
  void Run() {}
  void operator()() const {}
  void DoWork() const {}
  template <typename... Args> void operator()(Args... args) const {
    this->func_(std::forward<Args>(args)...);
  }

private:
  F func_;
};

#endif // __TASKIMPL_H__
class TaskWrapper {
public:
  TaskWrapper() = default;
......

public:
  template <typename F, typename... Args> void operator()(F &&f, Args... args) const {
    static_assert(!std::is_same_v<std::remove_cvref_t<F>, TaskImpl<F>>); // c++20
    using standType = TaskImpl<F>;
    auto static task = std::make_unique<standType>(std::forward<F>(f));
    task->operator()(std::forward<Args>(args)...);
  }

private:
  ......
};

当然,可以把“typedef void(*pFtask)()”替换成std::function,在前面的系列中基本都是这样做的。其实大家看很多的开源的线程池,在实际应用的场景下,一般参数都是固定的,所以他们很多参数都是固定的。这样的好处就在于不用和模板来回折腾,至于孰优孰劣,就见仁见智了。
而实际的线程运行,往往是对数据进行处理,这其实更多的是从一个队列中获取,这时参数的传递的意义更小甚至没有,所以开发者一定要根据实际情况来决定你的设计,不要盲目的追求高大上和普适性,这算是一点小的建议吧。

四、总结

线程池中任务的封装处理其实是相当重要的一环,毕竟外来的任务需要从此承载到运行的线程上。也只有这一块设计的灵活可扩展,才能使线程池本身的应用更容易扩展。其实任务的封装有的时候儿可以针对具体的实现来实现,不需要抽象到一定层次。但是一个良好的设计,一定是从顶层抽象良好的。
这本来就是一个矛盾,平衡点就在于开发者对开发的具体的要求和把控。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值