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

一、线程池中的任务

在前面的线程池操作中,任务只是通过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++20using 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,在前面的系列中基本都是这样做的。其实大家看很多的开源的线程池,在实际应用的场景下,一般参数都是固定的,所以他们很多参数都是固定的。这样的好处就在于不用和模板来回折腾,至于孰优孰劣,就见仁见智了。
而实际的线程运行,往往是对数据进行处理,这其实更多的是从一个队列中获取,这时参数的传递的意义更小甚至没有,所以开发者一定要根据实际情况来决定你的设计,不要盲目的追求高大上和普适性,这算是一点小的建议吧。

四、总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/812561.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【c++】优先级队列|反向迭代器(vector|list)

优先级队列的常用函数的使用 #include<iostream> #include<queue> using namespace std;int main() {priority_queue<int>st;st.push(1);st.push(7);st.push(5);st.push(2);st.push(3);st.push(9);while (!st.empty()){cout << st.top() << &qu…

UDP简单总结

UDP&#xff1a;用户数据报协议 特点: 无连接、不可靠通信 不事先建立连接&#xff0c;数据按照包发&#xff0c;一包数据包含&#xff1a;自己的IP、程序端口、目的地IP、程序端口和数据(限制在64KB内) 发送方不管对方是否在线&#xff0c;数据在中间丢失也不管&#xff0c;…

SpringBoot与MyBatisPlus的依赖版本冲突问题

记录使用SpringBoot和MyBatisPlus时遇到的版本冲突问题解决。 java版本&#xff1a;jdk17 废话&#xff1a;&#xff09;目前在IDEA中使用Spring官方的脚手架最低jdk版本竟然是jdk17了。 当使用SpringBoot3.0版本(3.2.4)&#xff0c;配合使用MP3.5.2版本时报错&#xff1a; Er…

对于所有对象都通用的方法⭐良好习惯总结

对于所有对象都通用的方法⭐良好习惯总结 Object是每个类的父类&#xff0c;它提供一些非final方法&#xff1a;equals、hashCode、clone、toString、finalize... 这些方法在设计上是可以被子类重写的&#xff0c;但是重写前需要遵守相关的规定&#xff0c;否则在使用时就可能…

应用实战|从头开始开发记账本2:基于模板快速开始

上期视频我们创建好了BaaS服务的后端应用。从这期视频开始&#xff0c;我们将从头开发一个互联网记账本应用。本期视频我们介绍一下如何使用模板快速开启我们的应用开发之旅。 应用实战&#xff5c;从头开始开发记账本2&#xff1a;基于模板快速开始 相关代码 本期视频我们介绍…

RestTemplate—微服务远程调用—案例解析

简介&#xff1a;总结来说&#xff0c;微服务之间的调用方式有多种&#xff0c;选择哪种方式取决于具体的业务需求、技术栈和架构设计。RESTful API和HTTP客户端是常见的选择&#xff0c;而Feign和Ribbon等辅助库可以简化调用过程。RPC和消息队列适用于特定的场景&#xff0c;如…

《由浅入深学习SAP财务》:第2章 总账模块 - 2.6 定期处理 - 2.6.4 月末操作:货币折算

2.6.4 月末操作&#xff1a;货币折算 如果一个公司代码启用了多个本位币&#xff0c;如下表所示&#xff0c;则在平时记账时&#xff0c;系统会在凭证行项目中同时体现出多个本位币的金额。 图2.6.4-1 两个本位币的金额都会实时更新到科目余额中。因此&#xff0c;在月末可以直…

pycharm2024关闭项目后一直显示正在关闭项目

网上的很多教程都试了不行&#xff0c;直接用下面的方法有效解决。 点击 帮助--查找操作--输入Registry--点注册表&#xff0c;取消ide.await.scope.completion后的勾选即可。

武汉星起航:跨境电商势头强劲,开启外贸增长新纪元

在全球化浪潮的推动下&#xff0c;跨境电商作为新兴贸易方式&#xff0c;正以前所未有的速度崛起。2024年前两个月&#xff0c;跨境电商进出口增长近10%&#xff0c;这一令人瞩目的数据&#xff0c;不仅彰显出跨境电商的强劲发展势头&#xff0c;更预示着其作为外贸新增长极的潜…

直接扩展到无限长,谷歌Infini-Transformer终结上下文长度之争

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了免费的人工智能中文站https://ai.weoknow.com 新建了收费的人工智能中文站https://ai.hzytsoft.cn/ 更多资源欢迎关注 不知 Gemini 1.5 Pro 是否用到了这项技术。 谷歌又放大招了&#xff0c;发布下一代 Transfor…

2016NOIP普及组真题 1. 买铅笔

线上OJ&#xff1a; 一本通&#xff1a;http://ybt.ssoier.cn:8088/problem_show.php?pid1973 核心思想&#xff1a; 向上取整的代码 (m (n-1))/n 。&#xff08;本题考点与2023年J组的第一和第二题一样&#xff09; 比如需要买31支笔&#xff0c;每包30支&#xff0c;则需要…

突破像素限制,尽显照片细腻之美——Topaz Gigapixel AI for Mac/Win

在这个数字化的时代&#xff0c;我们都热爱用照片记录生活中的美好瞬间。然而&#xff0c;有时候我们会发现&#xff0c;由于各种原因&#xff0c;照片的像素可能无法满足我们的需求。这时候&#xff0c;Topaz Gigapixel AI for Mac/Win 这款强大的照片放大工具应运而生。 Top…

代码开发常见模块引入顺序规范

第一组&#xff1a;第三方库或框架 React 相关模块第三方库或框架&#xff08;例如&#xff1a;antd&#xff09; 第二组&#xff1a;自己的库或模块 自己的库或模块&#xff08;例如&#xff1a;jszone/abc、jszone/bcd&#xff09; 第三组&#xff1a;具体功能或业务相关…

联储降息预期落空打了谁的脸

美国 3 月消费者价格指数&#xff08;CPI&#xff09;于本周发布&#xff0c;最新数据全线高于预期。具体而言&#xff0c;美国劳工部周三公布的数据显示&#xff0c;美国 3 月消费者物价指数&#xff08;CPI&#xff09;同比上涨 3.5%&#xff0c;为 2023 年 9 月以来最高水平…

华为OD-C卷-分割均衡字符串[100分]

题目描述 均衡串定义&#xff1a;字符串中只包含两种字符&#xff0c;且这两种字符的个数相同。 给定一个均衡字符串&#xff0c;请给出可分割成新的均衡子串的最大个数。 约定&#xff1a;字符串中只包含大写的 X 和 Y 两种字符。 输入描述 输入一个均衡串。 字符串的长…

python基础——类【类的定义和使用、魔术方法】

&#x1f4dd;前言&#xff1a; python中的类&#xff0c;自我感觉在某种程度上和C语言的结构体是有共同之处的&#xff0c;如果有兴趣&#xff0c;可以先看看这篇文章&#xff1a;C语言——结构体类型&#xff08;一&#xff09;&#xff0c;先了解一下C语言中的结构体&#x…

在Ubuntu上安装Docker Compose

Docker Compose 是一个用于定义和管理Docker容器的工具&#xff0c;它使用yml来配置应用的服务、网络和卷等。特别是在定义多个容器时&#xff0c;它非常擅长定义多个容器之间的关系和依赖。 第一步&#xff1a;更新软件包 sudo apt update第二步&#xff1a;安装网络工具cur…

基于FMC接口的Kintex-7 XC7K325T PCIeX4 3U PXIe接口卡

基于FMC接口的Kintex-7 XC7K325T PCIeX4 3U PXIe接口卡 一、板卡概述 本板卡基于Xilinx公司的FPGAXC7K325T-2FFG900 芯片&#xff0c;pin_to_pin兼容FPGAXC7K410T-2FFG900 &#xff0c;支持PCIeX8、64bit DDR3容量2GByte&#xff0c;HPC的FMC连接器&#xff0c;板卡支持PXI…

【QT教程】QML传感器融合应用

QML传感器融合应用 使用AI技术辅助生成 QT界面美化视频课程 QT性能优化视频课程 QT原理与源码分析视频课程 QT QML C扩展开发视频课程 免费QT视频课程 您可以看免费1000个QT技术视频 免费QT视频课程 QT统计图和QT数据可视化视频免费看 免费QT视频课程 QT性能优化视频免费看 免…

基于单片机的家用无线火灾报警系统设计

摘 要:针对普通家庭的火灾防范需求,设计一种基于单片机的家用无线智能火灾报警系统。该系统主要由传感器、单片机、无线通信模块、GSM 模块、输入显示模块、声光报警电路和GSM 报警电路组成。系统工作时,检测部分单片机判断是否发生火灾,并将信息通过无线通信模块传…