taskflow 源码阅读笔记-1

之前写了一篇介绍Taskflow的短文:传送门

Taskflow做那种有前后依赖关系的任务管理还是不错的,而且他的源码里运用了大量C++17的写法,觉得还是非常值得学习的,因此决定看一下他的源码,这里顺便写了一篇代码学习笔记。

概述

代码链接:

https://github.com/taskflow/taskflow
本文是commitid: b91df2c365c20fa4cb43951192f6939fbe876abf 版本的源码学习记录,其他版本可能会有不同

简介

简介可以参考代码仓库的README
简单来说它是一个实现DAG(有向无环图)的多线程任务管理库, 下图截取自代码仓库的README:
image.png

使用方法

使用方法见README,这里需要指出的是,作者分享了他的compile explorer https://godbolt.org/z/j8hx3xnnx, 我们可以在上面修改代码来看效果
这个是个纯头文件的库,只需要引用其头文件就行了
#include <taskflow/taskflow.hpp> // Taskflow is header-only
但是这个头文件怎么来的呢?
其实这个和Eigen的使用方法一样,在源码库中,有一个taskflow文件夹,里面全部都是.hpp文件,在最外层有一个taskflow.hpp文件,它是总的入口文件,我们只要include这个文件就行。
具体来说,如果我们想在我们自己的项目中使用它,
一种方法是直接将taskflow文件夹中的所有文件拷贝到我们自己的代码仓库,
另一种更好的方式就是和Eigen一样,把他做成一个deb包安装在机器上,这样我们就可以在多个项目中使用它了
不管怎么安装,最后在我们需要使用它的地方#include <taskflow/taskflow.hpp> 就行了
下面摘抄自代码仓库中的README:

#include <taskflow/taskflow.hpp>  // Taskflow is header-onlyint main(){tf::Executor executor;tf::Taskflow taskflow;auto [A, B, C, D] = taskflow.emplace(  // create four tasks[] () { std::cout << "TaskA\n"; },[] () { std::cout << "TaskB\n"; },[] () { std::cout << "TaskC\n"; },[] () { std::cout << "TaskD\n"; } );                                  A.precede(B, C);  // A runs before B and CD.succeed(B, C);  // D runs after  B and Cexecutor.run(taskflow).wait(); return 0;
}

代码学习

核心代码量

主要功能代码约1.5万行,在taskflow文件夹下:
image.png

代码实现

从上面的使用示例来看,它给我们展示了几个接口:

  • tf::Executor executor;
  • tf::Taskflow taskflow;
  • auto [A, B, C, D] = taskflow.emplace(… …);
  • A.precede(B, C); // A runs before B and C
  • D.succeed(B, C); // D runs after B and C
  • executor.run(taskflow).wait();

下面我们就从这几个接口入手来看其代码实现

tf::Taskflow

首先来看命名空间tf
tf是本代码仓库的根命名空间,所有的代码都是在这个命名空间下的
然后我们来看tf::Taskflow
代码位于 taskflow/taskflow/core/taskflow.hpp

定义tf::Taskflow taskflow时,
首先会构造其成员变量
其中实际会执行构造的只有 Graph _graph;
我们看下_graph的构造过程:
Gragh类只有一个成员变量 std::vector<Node*> _nodes; 它是一个指针的数组,因此也没什么额外的构造过程
再来看Graph类的无参构造函数,发现它直接是用的default构造函数,因此它也没做什么事情。
然后会调用构造函数:
因为没有传入参数,所以调用的是无参构造函数:

// Constructor
inline Taskflow::Taskflow() : FlowBuilder{_graph} {
}

Taskflow的构造函数啥都没有干,我们再看它的基类FlowBuilder的构造函数
FlowBuilder构造时需要传入_graph变量,这个变量是Taskflow类的成员变量,后面再看它是怎么构造的

// Constructor
inline FlowBuilder::FlowBuilder(Graph& graph) :_graph {graph} {
}

FlowBuilder的构造函数也啥都没有干,只是把_graph赋值给它自己的成员变量,注意Graph类只有移动构造函数,也就是说,此时Taskflow类中的_graph已经报废了
因为FlowBuilder类中只有_graph这一个成员变量,因此在构造阶段它没啥别的事情了

taskflow.emplace(…)

Taskflow类里没有emplace(…)这个方法,它是Taskflow的基类FlowBuilder的成员函数
emplace(…)方法有5个实现版本, 其中一个的定义如下:

template <typename C, std::enable_if_t<is_static_task_v<C>, void>*>
Task FlowBuilder::emplace(C&& c) {return Task(_graph._emplace_back("", 0, nullptr, nullptr, 0,std::in_place_type_t<Node::Static>{}, std::forward<C>(c)));
}

然后,作者在is_static_task_v的地方实现了不同版本的萃取方法:

  • is_static_task_v
  • is_dynamic_task_v
  • is_condition_task_v
  • is_multi_condition_task_v
  • sizeof…(C )>1

示例代码中传入的是多个lamda表达式, 会匹配到 sizeof…©>1 这个版本,代码如下:

template <typename... C, std::enable_if_t<(sizeof...(C)>1), void>*>
auto FlowBuilder::emplace(C&&... cs) {return std::make_tuple(emplace(std::forward<C>(cs))...);
}

然后再依次调用 is_static_task_v 的版本,代码如上所示:
做的事情是:

  • 用传入的lamda表达式构造了一个Node,
  • 然后把这个Node放到_graph中,
  • 最后用_graph._emplace_back返回的Node*来构造一个Task实例返回

A.precede(B, C)

从上面的分析可以知道, A/B/C都是Task类的实例,
先来看下Task类的构造:

inline Task::Task(Node* node) : _node {node} {
}

Task类中没有其他成员变量,只有一个Node* __node, 因此Task中只维护一个Node, Task类是Node类的观察者

precede() 是Task类的方法, 它的实现如下:

template <typename... Ts>
Task& Task::precede(Ts&&... tasks) {(_node->_precede(tasks._node), ...);//_precede(std::forward<Ts>(tasks)...);return *this;
}

实际上是执行的Node类的precede
再来看Node类precede函数

inline void Node::_precede(Node* v) {_successors.push_back(v);v->_dependents.push_back(this);
}

这里涉及到两个变量:

SmallVector<Node*> _successors;  // 下一个要执行的节点
SmallVector<Node*> _dependents;  // 上一个执行的节点

这样用这两个向量把依赖关系保存起来

D.succeed(B, C)

和上一个函数类似,只是依赖关系改了一下:

template <typename... Ts>
Task& Task::succeed(Ts&&... tasks) {(tasks._node->_precede(_node), ...);//_succeed(std::forward<Ts>(tasks)...);return *this;
}

tf::Executor

Executor类的构造过程如下:

// 声明
explicit Executor(size_t N = std::thread::hardware_concurrency());// 定义
inline Executor::Executor(size_t N) :_MAX_STEALS {((N+1) << 1)},_threads    {N},_workers    {N},_notifier   {N} {if(N == 0) {TF_THROW("no cpu workers to execute taskflows");}_spawn(N);// instantite the default observer if requestedif(has_env(TF_ENABLE_PROFILER)) {TFProfManager::get()._manage(make_observer<TFProfObserver>());}
}

我们用的示例是无参的,因此N默认为std::thread::hardware_concurrency(), 即当前系统支持的并发线程数的估计值
然后设置:

  • _MAX_STEALS 为 (N+1)*2
  • 实例化N个std::thread
  • 实例化N个worker
  • 实例化参数为N的notifier

然后调用_spawn()函数启动任务

executor.run(taskflow).wait()

inline tf::Future<void> Executor::run(Taskflow& f) {return run_n(f, 1, [](){});
}template <typename C>
tf::Future<void> Executor::run_n(Taskflow& f, size_t repeat, C&& c) {return run_until(f, [repeat]() mutable { return repeat-- == 0; }, std::forward<C>(c));
}template <typename P, typename C>
tf::Future<void> Executor::run_until(Taskflow&& f, P&& pred, C&& c) {std::list<Taskflow>::iterator itr;{std::scoped_lock<std::mutex> lock(_taskflows_mutex);itr = _taskflows.emplace(_taskflows.end(), std::move(f));itr->_satellite = itr;}return run_until(*itr, std::forward<P>(pred), std::forward<C>(c));
}

TODO: 这里还有很多没看的,先写到这里,有空继续补充。。。

用到C++功能

这个代码库使用了大量的modern C++的特性,下面列举一些:

  • std::forward
  • std::future
  • std::function
  • lamda表达式
  • 模版元编程
  • std::atomic
  • std::decay_t
  • std::is_void_v
  • std::monostate
  • std::tuple
  • std::make_tuple
  • std::get
  • std::array
  • std::index_sequence
  • std::memory_order_relaxed
  • std::is_invocable_v
  • std::add_lvalue_reference_t
  • std::find_if
  • std::find_if_not
  • std::distance
  • std::next
  • std::min_element
  • std::advance
  • std::lock_guard
  • std::mutex
  • std::invoke
  • std::enable_if_t
  • std::memcmp

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

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

相关文章

Amazon 亚马逊新玩具——在线购物试衣服“虚拟试穿”模型:Diffuse to Choose

这个模型拥有强大的能力&#xff0c;它能够将任何商品无缝地融入任何环境之中&#xff0c;确保商品与环境完美匹配。例如&#xff0c;你可以轻松地将在线商店中的椅子图片放入你客厅照片中&#xff0c;预览它实际摆放的效果。无论环境如何变化&#xff0c;该模型都能确保商品展…

Java复习系列之阶段二:数据库

1. 基础语法 1.1 DQL&#xff08;数据查询语句&#xff09; 执行顺序&#xff1a; from、join 、on、where、group by、having、select、distinct、order by、limit 1.2 DML&#xff08;数据修改语言&#xff09; 对数据表的增删改 insert into update set delete form 1.…

【JavaEE进阶】 #{}和${}

文章目录 &#x1f343;前言&#x1f333;#{}和${}使⽤&#x1f6a9;Interger类型的参数&#xff08;基础数据类型&#xff09;&#x1f388;使用#{}&#x1f388;使用${} &#x1f6a9;String类型的参数使用&#x1f388;#{}使用&#x1f388;${} &#x1f38d;#{}和${}区别&a…

C++:引用

目录 概念&#xff1a; 引用的使用格式&#xff1a; 引用特性&#xff1a; 常引用 使用场景&#xff1a; 1、做参数 二级指针时的取别名 一级指针取别名 一般函数取别名 2、做返回值 函数返回值的原理&#xff1a; 引用的返回值使用&#xff1a; 引用和指针的对比&…

Java Swing桌面项目打包成可执行jar

前言 最近有需求&#xff0c;将Swing项目打包为一个可执行的jar包&#xff0c;遇见了一些问题&#xff0c;参考AI助手&#xff0c;解决了遇到的问题&#xff0c;也有一些亲身实践体会&#xff0c;记录一下。开发环境IntelliJ IDEA&#xff0c;JDK8&#xff0c;用kotlin语言实现…

navicat连接postgresql、人大金仓等数据库报错

navicat连接postgresql、人大金仓数据库报错问题是一个偶现的问题&#xff0c;需要我们特别关注&#xff1a; 1、客户端连接人大金仓数据库 这里注意&#xff1a;navicat连接postgresql、人大金仓数据库时均选择postgresql类型&#xff0c;因为人大金仓数据库底层和psql数据库…

【第五天】蓝桥杯备战

1、金币 https://www.lanqiao.cn/problems/357/learning/ 解法&#xff1a;暴力 import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改public class Main {public static void main(String[] args) {Scanner scan new Scanner(System.in);//在此输入…

01_Anaconda环境搭建

概述 Anaconda包含了许多常用的科学计算和数据分析的库和工具。通过Anaconda&#xff0c;用户可以更方便地安装、管理和更新这些库和工具&#xff0c;从而提高工作效率。 Anaconda还提供了一个名为conda的包管理器&#xff0c;可以帮助用户方便地安装、管理和更新这些库和工具…

写静态页面——魅族声学_前端页面练习

1、效果: 1、html代码: <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>魅族声学</titl…

金智易表通构建学生缴费数据查询+帆软构建缴费大数据报表并整合到微服务

使用金智易表通挂接外部数据,快速建设查询类服务,本次构建学生欠费数据查询,共有3块设计,规划如下: 1、欠费明细查询:学校领导和财务处等部门可查询全校欠费学生明细数据;各二级学院教职工可查询本二级学院欠费学生明细数据。 2、大数据统计报表:从应收总额、欠费总额…

Spring与Redis集成

1.引入RedisTemplate 据以前的情况&#xff0c;我们在Java中使用Redis时一般是使用Jedis来操作的&#xff0c;大致的一段代码如下所示 Overridepublic User findUserById(Integer id) {User user null;Jedis jedis null;try {jedis jedisPool.getResource();String userStr…

直接在GitHub上使用vscode阅读源码

方法&#xff1a; 直接在github后面输入1s即可 效果&#xff1a;

光学系统的核心--分辨率

前言 在机器视觉领域&#xff0c;可以把各个部件划分为光源&#xff0c;镜头&#xff0c;相机&#xff0c;采集卡&#xff0c;算法&#xff0c;运动平台等。各个部件都是系统的有机组合&#xff0c;均有各自的重要性。在实际应用中&#xff0c;成像镜头涉及的光学理论较多&…

[数据结构]-哈希

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 本期学习目标&…

智能工厂4G无线设备预测维护云端联动的DI、AI、DO混合信号处理单元

在现代工业智能化进程中&#xff0c;一款集成了丰富I/O接口并能与各大云平台无缝对接的智能设备显得尤为重要。比如最近推出的这款创新产品&#xff0c;它集合了8路数字输入通道&#xff0c;涵盖了干湿节点的识别功能&#xff0c;适用于多种开关量信号的读取&#xff1b;同时&a…

(八)springboot实战——springboot3下的webflux项目全局异常处理

前言 在webflux响应式编程中&#xff0c;如何处理系统运行时异常是本节的主要内容。在传统的Servlet阻塞式web项目中主要通过HandlerExceptionResolver处理器来处理&#xff0c;而在webflux响应式web项目中&#xff0c;则是通过DispatchExceptionHandler异常处理器来处理异常。…

[SWPUCTF 2018]SimplePHP1

打开环境 有查看文件跟上传文件&#xff0c;查看文件里面显示没有文件url貌似可以文件读取 上传文件里面可以上传文件。 先看一下可不可以文件读取 /etc/passwd不能读取&#xff0c;源码提示flag在f1ag.php 看看能不能读取当前的文件&#xff0c; 先把代码摘下来 file.php …

JavaScript 之 作用域变量提升闭包

一、JavaScript 代码的执行 浏览器内核是由两部分组成的&#xff0c;以 webkit 为例 WebCore&#xff1a;负责HTML解析、布局、渲染等等相关的工作JavaScriptCore&#xff1a;解析、执行 JavaScript 代码 另外一个强大的 JavaScript 引擎就是 V8 引擎 二、深入 V8 引擎原理 …

Java面向对象三大特征之多态

在之前的文章&#xff0c;我们分别介绍了类与对象、面向对象三大特征的封装、以及继承&#xff08;一&#xff09;、继承&#xff08;二&#xff09;。这一篇文章&#xff0c;我们介绍Java面向对象三大特征的最后一个——多态。 多态 多态的概述 概念&#xff1a;完成某个行为…

CVE-2024-23897 Jenkins 任意文件读取漏洞

项目介绍 Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件项目可以进行持续集成。Jenkins是开源CI&CD软件领导者&#xff0c; 提供超过1000个插…