taskflow 源码阅读笔记-1

news2024/10/1 1:19:49

之前写了一篇介绍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-only

int 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 C
  D.succeed(B, C);  // D runs after  B and C
                                      
  executor.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 requested
  if(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.coloradmin.cn/o/1413420.html

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

相关文章

【深度学习】sdxl中的 text_encoder text_encoder_2 区别

镜像问题是&#xff1a;https://editor.csdn.net/md/?articleId135867689 代码仓库&#xff1a; https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/tree/main 截图&#xff1a; 为什么有两个CLIP编码器 text_encoder 和 text_encoder_2 &#xff1f; 在…

vivado DDS学习

实现DDS通常有两种方式&#xff0c;一种是读取ROM存放的正弦/余弦信号的查表法&#xff0c;另一种是用DDS IP核。这篇学习笔记中&#xff0c;我们要讲解说明的是VIVADO DDS IP核的应用。 目前本篇默认Phase Generator and SIN/COS LUT&#xff08;DDS&#xff09;的standard模式…

运行时提示d3dx9_43.dll错误的解决办法,d3dx9_43.dll为什么会丢失

当你在运行某些程序或游戏时出现"找不到d3dx9_43.dll"的错误提示时&#xff0c;不要慌张&#xff01;这是一个常见的问题&#xff0c;但有多种有效的解决办法可以帮助你修复这个文件丢失的情况。今天就来教大家d3dx9_43.dll文件丢失的多种解决办法。 一、d3dx9_43.dl…

【微信小程序】怎样创建formdata对象,并通过 wx.request 发送file文件

一、场景 业务需求&#xff1a;微信小程序开发需要选择本地照片file连同一些表单数据&#xff0c;通过接口发送给服务器 问题&#xff1a; 因涉及到域名安全问题以及并不是单独上传图片&#xff0c;不能使用 wx.uploadFile 。 微信本身没有FormData对象&#xff0c;无法使用 n…

【yaml 文件使用】pytest+request 框架中 yaml 配置文件使用

又来进步一点点~~ 背景&#xff1a;最近在学习pytestrequest框架写接口测试自动化&#xff0c;使用yaml文件配置更方便管理用例中的数据&#xff0c;这样更方便 yaml 介绍&#xff1a; 什么是 yaml 文件&#xff1a;YAML 是 “YAML Ain’t a Markup Language”&#xff08;Y…

硬件知识(1) 手机的长焦镜头

#灵感# 手机总是配备好几个镜头&#xff0c;研究一下 目录 手机常配备的摄像头&#xff0c;及效果举例 长焦的焦距 焦距的定义和示图&#xff1a; IPC的焦距和适用场景&#xff1a; 手机常配备的摄像头&#xff0c;及效果举例 以下是小米某个手机的摄像头介绍&#xff1a…

防御保护----防火墙的安全策略、NAT策略实验

实验拓扑&#xff1a; 实验要求&#xff1a; 1.生产区在工作时间&#xff08;9&#xff1a;00-18&#xff1a;00&#xff09;内可以访问DMZ区&#xff0c;仅可以访问http服务器&#xff1b; 2.办公区全天可以访问DMZ区&#xff0c;其中10.0.2.10可以访问FTP服务器和HTTP服务器…

动能资讯 | 新能源充电桩

充电桩是新能源汽车快速发展不可或缺的一环。充电桩是为新能源汽车充电的充电设施&#xff0c;类似于加油站里的加油机&#xff0c;安装于公共建筑和居民小区停车场或充电站内&#xff0c;可以根据不同的电压等级为各种型号的电动汽车充电。 按照其输出的电压电流为交流电还是…

单片机学习笔记---矩阵键盘密码锁

目录 一&#xff0c;设置密码按键 1.设置密码区域 2.设置输入的数字左移 3.设置记录按键的次数 二&#xff0c;设置确认键 1.密码正确时显示OK 2.密码错误时显示ERR 3.密码错误恢复初始状态重输 三&#xff0c;设置取消键 学了这么久&#xff0c;迫不及待想要做一个密…

虹科数字化与AR部门升级为安宝特AR子公司

致关心虹科AR的朋友们&#xff1a; 感谢您一直以来对虹科数字化与AR的支持和信任&#xff0c;为了更好地满足市场需求和公司发展的需要&#xff0c;虹科数字化与AR部门现已升级为虹科旗下独立子公司&#xff0c;并正式更名为“安宝特AR”。 ”虹科数字化与AR“自成立以来&…

力扣hot100 实现Trie(前缀树) 字典树 一题双解

Problem: 208. 实现 Trie (前缀树) 文章目录 思路复杂度&#x1f49d; TrieNode版&#x1f49d; 二维数组版 思路 &#x1f469;‍&#x1f3eb; 宫水三叶 复杂度 &#x1f49d; TrieNode版 public class Trie {class TrieNode{boolean end;//标记是否有以当前节点为结尾的字…

算法笔记:地理探测器

1 空间分层异质性&#xff08;spatial stratified heterogeneity&#xff09; 空间分层异质性&#xff08;空间分异性/区异性&#xff09;&#xff1a;层内方差小于层间方差的地理现象例如气 候带、土地利用图、地貌图、生物区系、区际经济差异、城乡差异以及主体功能区等 等[…

Git 删除已经 Push 到远程多余的文件

例如要删除 data/log 文件 1. 在当前项目下打开终端 2. 查看有哪些文件夹 dir 3. 预览将要删除的文件&#xff08;如果不清楚该目录下是否存在不应该删除的文件&#xff09; git rm -r -n --cached 文件/文件夹名称 加上 -n 这个参数&#xff0c;执行命令时&#xff0c;是不会…

vue 样式隔离原理

日常写单文件组件时&#xff0c;会在style添加scoped属性&#xff0c;如<style scoped>&#xff0c;目的是为了隔离组件与组件之间的样式&#xff0c;如下面的例子&#xff1a; <template><p class"foo">这是foo</p><p class"bar&q…

React16源码: React中commitAllHostEffects内部的commitDeletion的源码实现

commitDeletion 1 &#xff09;概述 在 react commit 阶段的 commitRoot 第二个while循环中调用了 commitAllHostEffects&#xff0c;这个函数不仅仅处理了新增节点&#xff0c;更新节点最后一个操作&#xff0c;就是删除节点&#xff0c;就需要调用 commitDeletion&#xff0…

SAP包的操作-修改程序所在的包

SAP包的操作 目录 SAP包的操作SAP GUI转换程序所在的包Eclipse转换程序所在的包 SAP GUI转换程序所在的包 Eclipse转换程序所在的包

YOLOv8优化策略:分层特征融合策略MSBlock | YOLO-MS ,超越YOLOv8与RTMDet,即插即用打破性能瓶颈

🚀🚀🚀本文改进:分层特征融合策略MSBlock,即插即用打破性能瓶颈 🚀🚀🚀在YOLOv8中如何使用 1)作为MSBlock使用;2)与c2f结合使用; 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1)手把…

Docker数据卷挂载(以容器化Mysql为例)

数据卷 数据卷是一个虚拟目录&#xff0c;是容器内目录与****之间映射的桥梁 在执行docker run命令时&#xff0c;使用**-v 本地目录&#xff1a;容器目录**可以完成本地目录挂载 eg.Mysql容器的数据挂载 1.在根目录root下创建目录mysql及三个子目录&#xff1a; cd ~ pwd m…

SpringMVC 环境搭建入门

SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架&#xff0c;属于SpringFrameWork 的后续产品&#xff0c;已经融合在 Spring Web Flow 中。 SpringMVC 已经成为目前最主流的MVC框架之一&#xff0c;并且随着Spring3.0 的发布&#xff0c;全面…

2024年材料、控制工程与制造技术国际学术会议(ICMCEMT 2024)

2024年材料、控制工程与制造技术国际学术会议(ICMCEMT 2024) 2024 International Conference on Materials, Control Engineering, and Manufacturing Technology (ICMCEMT 2024) 会议简介&#xff1a; 2024年材料、控制工程与制造技术国际学术会议(ICMCEMT 2024)定于2024年在…