0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

workflow的任务模型

汽车电子技术 来源: 程序喵大人 作者: 程序喵大人 2023-02-21 14:05 次阅读

今天,想聊聊workflow这个开源项目。

关于workflow,我之前特意写过一篇文章【推荐学习这个C++开源项目】。

今天还是想再啰嗦啰嗦,因为自己这一年也在带团队从0到1做项目,需要负责整个项目的架构设计、接口设计、模块划分等工作。

做了一段时间后再回过头复盘一下,深知架构设计、接口设计的重要性,也感受到了架构设计的困难程度,编码和设计相比,真的容易的多了。

然后自己又回头来研究了一下workflow,想着学习下其他项目的设计理念,随着自己研究的越来越深入,越来越感觉它的高端,对外暴露特别简单的接口却能完成非常复杂的功能。

上篇文章是基础篇,主要向大家普及一下workflow的特点和作用,感兴趣的朋友可以移步到那里哈。

本篇文章是进阶篇,主要就是想介绍下workflow的任务模型,其他的框架一般只能处理普通的网络通信,而workflow却特别适用于通信与计算关系很复杂的应用。其实我最感兴趣的是它的内存管理机制,下面也会详细介绍。

图片

图片

优秀的系统设计

图片

图片

在作者的设计理念中:程序 = 协议 + 算法 + 任务流。

**协议:**就是指通用的网络协议,比如http、redis等,当然还可以自定义网络协议,这里只需要提供序列化和反序列化函数就可以达到想要的效果。

算法: workflow提供了一些通用的算法,比如sort、merge、reduce等,当然还可以自定义算法,用过C++ STL的朋友应该都知道如何自定义算法吧,在workflow中,任何复杂的计算都应该包装成算法。

**任务流:**我认为这应该就是整个系统设计的核心,通过任务流来抽象封装实际的业务逻辑,就是把开发好的协议和算法组成一个任务流程图,然后调度执行这个图。

图片

图片

任务流

图片

图片

这里多聊聊任务流,在workflow中,一切业务逻辑皆是任务,多个任务会组成任务流,任务流可组成图,这个图可能是串联图,可能是并联图,也有可能是串并联图,类似于这种:

图片

也可能是这种复杂的DAG图:

图片

图的层次结构可以由用户自定义,框架也是支持动态地创建任务流。

引用作者的一句话:

图片

图片

如果把业务逻辑想象成用设计好的电子元件搭建电路,那么每个电子元件内部可能又是一个复杂电路。

workflow对任务做了很好的抽象和封装。整个系统包含6种基础任务:通讯、文件IO、CPU定时器、计数器。

workflow提供了任务工厂,所有的任务都由任务工厂产生,并且会自动回收。

大多数情况下,通过任务工厂创建的任务都是一个复合任务,但用户并不感知。例如一个http请求,可能包含很多次异步过程(DNS,重定向),内部有很多复杂的任务,但对用户来讲,这就是一次简单的通信任务。

哪有什么岁月静好,只不过是有人替你负重前行。workflow的一大特点就是接口暴露的特别简洁,非常方便用户的接入,外部接入如此简单,肯定是将很多组合的逻辑都放在了内部,但其实workflow项目内部代码结构层次非常简洁清晰,感兴趣的朋友可以自己研究研究哈。

图片

图片

内存管理机制

图片

图片

还有就是项目的内存管理机制,workflow整个项目都遵循着谁申请谁释放的原则,内部申请的内存不需要外部显式释放,内部会自动回收内存。

而且整个项目都没有使用shared_ptr,那多个对象共同使用同一块裸内存,workflow是怎么处理的呢?

内部为这种需要共享的对象各自维护一个引用计数,手动incref和decref,至于为什么要这样做,可以看看workflow美女架构师的回答【https://www.zhihu.com/question/33084543/answer/2209929271】。

我总结了一下:

  • shared_ptr是非侵入式指针,一层包一层,而且为了保持shared_ptr覆盖对象整个生命周期,每次传递时都必须带着智能指针模板,使用具有传染性,而且也比较麻烦。
  • shared_ptr引用计数的内存区域和数据区域不一致,不连续,缓存失效可能导致性能问题,尽管有make_shared,但还是容易用错。
  • 手动为对象做incref和decref,使用起来更灵活,可以明确在引用计数为固定数字时做一些自定义操作,而且方便调试。因为手动管理对象的引用计数,就会要求开发者明晰对象的生命周期,明确什么时候该使用对象,什么时候该释放对象。
  • 如果使用shared_ptr可能会激起开发者的惰性,反正也不需要管理内存啦,就无脑使用shared_ptr呗,最后出现问题时调试起来也比较困难。

那再深入源码中研究研究,看看workflow是如何做到把对象指针给到外部后,内部还自动回收的。

拿WFClientTask举例说明一下,workflow中所有的Task都是通过Factory创建:

static WFHttpTask *create_http_task(const std::string& url,
                    int redirect_max,
                    int retry_max,
                    http_callback_t callback);


using WFHttpTask = WFNetworkTask;

template <class REQ, class RESP>
class WFClientTask : public WFNetworkTask {};

注意,create参数里有一个callback,workflow一定会执行这个callback,然后内部回收掉该WFClientTask占用的内存,任何任务的生命周期都是从创建到callback函数结束。

它是怎么做到的?继续看下WFClientTask的继承层次结构:

template <class REQ, class RESP>
class WFClientTask : public WFNetworkTask {};


template<class REQ, class RESP>
class WFNetworkTask : public CommRequest {};


class CommRequest : public SubTask, public CommSession {};


class SubTask {
public:
  virtual void dispatch() = 0;
private:
  virtual SubTask *done() = 0;
protected:
  void subtask_done();
};

WFClientTask继承于WFNetworkTask,WFNetworkTask又继承于SubTask。

SubTask内部有subtask_done()方法,看下它的实现:

void SubTask::subtask_done() {
  SubTask *cur = this;
  ParallelTask *parent;
  SubTask **entry;


  while (1) {
    parent = cur->parent;
    entry = cur->entry;
    cur = cur->done();
    if (cur) {
      cur->parent = parent;
      cur->entry = entry;
      if (parent)
        *entry = cur;
      cur->dispatch();
    }
    else if (parent) {
      if (__sync_sub_and_fetch(&parent->nleft, 1) == 0) {
        cur = parent;
        continue;
      }
    }
    break;
  }
}

subtask_done()方法中会调用它的done()方法,然而这几个方法都是virtual方法,看看WFClientTask是怎么重写它们的:

template <class REQ, class RESP>
class WFClientTask : public WFNetworkTask<REQ, RESP> {
protected:
  virtual SubTask *done() {
    SeriesWork *series = series_of(this);
    if (this->state == WFT_STATE_SYS_ERROR && this->error < 0) {
      this->state = WFT_STATE_SSL_ERROR;
      this->error = -this->error;
    }
    if (this->callback)
      this->callback(this);
    delete this;
    return series->pop();
  }
};

子类重写了done()方法,可以看到在它的实现里,触发了callback,然后调用了delete this,释放掉了当前占用的这块内存。

那谁调用的done(),可以看下上面的代码,subtask_done()会触发done(),那谁触发的subtask_done():

void CommRequest::dispatch() {
  if (this->scheduler->request(this, this->object, this->wait_timeout,
                 &this->target) < 0) {
    this->state = CS_STATE_ERROR;
    this->error = errno;
    if (errno != ETIMEDOUT)
      this->timeout_reason = TOR_NOT_TIMEOUT;
    else
      this->timeout_reason = TOR_WAIT_TIMEOUT;
    this->subtask_done();
  }
}

可以看到,dispatch()里触发了subtask_done(),那谁触发的dispatch():

template<class REQ, class RESP>
class WFNetworkTask : public CommRequest {
public:
  /* start(), dismiss() are for client tasks only. */
  void start() {
    assert(!series_of(this));
    Workflow::start_series_work(this, nullptr);
  }
};


inline void
Workflow::start_series_work(SubTask *first, SubTask *last,
              series_callback_t callback) {
  SeriesWork *series = new SeriesWork(first, std::move(callback));
  series->set_last_task(last);
  first->dispatch();
}

这里可以看到,Task的start()方法触发start_series_work(),进而触发dispatch()方法。

总结一下:

● 步骤一

通过工厂方法创建WFClientTask,同时设置callback;

● 步骤二

外部调用start()方法,start()中调用Workflow::start_series_work()方法;

● 步骤三

start_series_work()中调用SubTask的dispatch()方法,这个dispatch()方法由SubTask的子类CommRequest(WFClientTask的父类)实现;

● 步骤四

dispatch()方法在异步操作结束后会触发subtask_done()方法;

● 步骤五

subtask_done()方法内会触发done()方法;

● 步骤六

done()方法内会触发callback,然后delete this;

● 步骤七

内存释放完成。

其实这块可以猜到,想要销毁自己的内存,基本上也就delete this这个方法。

然而我认为这中间调用的思想和过程才是我们需要重点关注和学习的,远不止我上面描述的这么简单,感兴趣的朋友可以自己去研究研究哈。

关于workflow还有很多优点,这里就不一一列举啦,它的源码也值得我们CPP开发者学习和进阶,具体可以看我之前的文章。

发现workflow团队对这个项目相当重视,还特意建了个QQ交流群(群号码是618773193),对此项目有任何问题都可以在这个群里探讨,也方便了我们学习,真的不错。项目地址如下:https://github.com/sogou/workflow,也可以点击阅读原文直达。

在访问GitHub遇到困难时,可使用他们的Gitee官方仓库:https://gitee.com/sogou/workflow。

项目也特别实用,据说搜狗内外很多团队都在使用workflow。感觉这个项目值得学习的话就给人家个star,不要白嫖哈,对项目团队来说也是一种认可和鼓励。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 框架
    +关注

    关注

    0

    文章

    398

    浏览量

    17416
  • 网络通信
    +关注

    关注

    4

    文章

    784

    浏览量

    29745
  • workflows
    +关注

    关注

    0

    文章

    6

    浏览量

    5917
收藏 人收藏

    评论

    相关推荐

    老生常谈---一种裸奔多任务模型

    一种裸奔多任务模型一个网友的总结:stateMachine + timerTick + queue。在RTOS环境下的多任务模型任务通常阻塞在一个OS调用上(比如从消息队列取数据)。外部如果想让该
    发表于 12-08 10:13

    裸奔环境下的多任务模型

    对于简单的嵌入式应用多数裸奔就能解决,但写出来的裸奔代码质量也由好坏之分。在网上看到了这样一篇文字:上面说到了裸奔环境下的多任务模型 - stateMachine + timerTick
    发表于 01-21 07:41

    allegro中workflow manager求解

    allegro中analysis菜单下使用workflow manager,点击start analysis后显示implement analysis failed或者coupling analysis failed?
    发表于 03-07 22:02

    Stage模型深入解读

    基于Stage模型开发应用,下面将会从应用组件、进程模型、线程模型任务模型、后台运行机制、应用配置文件6个方面进行介绍。 1、组件模型
    发表于 03-15 10:32

    OPC 实时任务系统动态调度算法的研究与设计The Stud

    本文基于已有的OPC Server 实时任务模型,设计了处理混合任务集的动态调度算法(基于截止期优先)和实现方式。该算法实现了对混合任集可调度性的判断,可以完成有硬实时性要
    发表于 05-31 15:36 13次下载

    一种基于角色和任务的访问控制模型

    在基于角色的访问控制模型的基础上引入了任务(task)和任务实例(task instance)的概念,建立了基于角色和任务的访问控制模型(R
    发表于 08-05 16:30 8次下载

    控制系统中实时任务分析

    本文分析了控制系统任务的特点,给出了控制系统中各种实时任务模型。分析了控制系统性能与任务参数之间的关系,给出了参数的设置方法。最后,研究了控制系统中实时任务
    发表于 08-06 08:35 10次下载

    基于页的8051多任务模型

      随着8051微控制器性能的不断提高,使用多任务操作系统对单片机进行资源管理已成为当代开发的需要。由于受静态链接的限制,8051系统的多任务开发需要处理代码重入(reentran
    发表于 09-25 17:34 964次阅读
    基于页的8051多<b class='flag-5'>任务模型</b>

    基于改进蜂群算法的多维QoS云计算任务调度算法

    针对云计算环境下用户日益多样化的QoS需求和高效的资源调度要求,提出了基于改进蜂群算法的多维QoS云计算任务调度算法,其中包括构建任务模型、云资源模型和用户QoS模型。为了获得高效的调
    发表于 12-01 16:11 0次下载

    嵌入式多核处理器任务调度研究

    任务模型而选择粒子群算法的适应度函数,综合利用局部最优极值和全局最优极值的优势,优化了粒子群算法中存在的过早收敛问题,使算法具有较高的收敛效率。实验结果表明,与基于遗传算法的多核多线程任务调度算法相比,该算法
    发表于 01-17 17:49 1次下载
    嵌入式多核处理器<b class='flag-5'>任务</b>调度研究

    一种基于神经网络的联合识别方法

    从事件时序关系与因果关系的关联性出发,提出基于神经网络的联合识别方法。将时序关系和因果关系识别分别作为主任务和辅助任务,设计共享辅助任务中编码层、解码层和编解码层的3种联合识别模型,通
    发表于 03-25 15:47 11次下载
    一种基于神经网络的联合识别方法

    开源软件-Sogou C++ Workflow高性能C++服务器引擎

    ./oschina_soft/gitee-workflow.zip
    发表于 06-20 09:36 1次下载
    开源软件-Sogou C++ <b class='flag-5'>Workflow</b>高性能C++服务器引擎

    模型任务的评价指标体系

    1. 写在前面 模型“好”与“坏”的评价指标直接由业务目标/任务需求决定。我们需要做的是:根据具体的业务目标/任务需求去选择相应的评价指标,继而选出符合业务目标/任务需求的好
    的头像 发表于 01-11 10:10 837次阅读

    基于M55H的定制化backbone模型AxeraSpine

    Backbone模型是各种视觉任务训练的基石,视觉任务模型的性能和模型的速度都受backbone模型的影响
    的头像 发表于 10-10 16:09 906次阅读
    基于M55H的定制化backbone<b class='flag-5'>模型</b>AxeraSpine

    workflow异步任务调度编程范式

    workflow是一个异步任务调度编程范式,封装了6种异步资源:CPU计算、GPU计算、网络、磁盘I/O、定时器、计数器,以回调函数模式提供给用户使用,概括起来实际上主要是两个功能:1、屏蔽阻塞调用的影响,使阻塞调用的开发接口变为异步的,充分利用计算资
    的头像 发表于 11-09 09:42 466次阅读
    <b class='flag-5'>workflow</b>异步<b class='flag-5'>任务</b>调度编程范式