百度统计
一面之猿网
让这个世界,因为我,有一点点的不一样
纯序员给你介绍图化框架的简单实现——条件判断

大家好,我是不会写代码的纯序员——Chunel。

在写程序的过程中,大家经常会遇到一些条件判断逻辑。就拿我来说吧,多年的工作经历,已经让我熟练掌握的通过编程来实现分支判断逻辑。代码如下:

if (true) {
    printf("yes");
} else {
    printf("输出啥已经不重要了");
}

还有,工作中可能还会遇到一些不太确定的选择。我举个例子(咳咳,注意了,开始上路了)哈:一个工作流程(pipeline)中包含了a、b、c三个流程,我们执行这个流程的时候,需要根据“某个/某些条件”从中选择一个执行。这个条件,可能跟当前的pipeline完全没有关系;也可能和当前pipeline之前的一些内容有关;还有可能跟这个pipeline的历史有关。

image.png

今天,我们主要来说一下,CGraph框架是如何在多个算子中,通过condition实现条件判断功能的。提前声明一下,看这一篇内容之前,需要把上一篇 《纯序员给你介绍图化框架的简单实现——参数传递》 的内容了解一下。

// 首先,std::cout << "源码链接 : https://github.com/ChunelFeng/CGraph " << std::endl;

功能介绍

我们再来看一下下面这张图哈:

image

主要看一下condition模块上下游的一条关系链:element->group->condition->function condition。其中,function condition中,又包含了function node-1/2/3 三个功能节点。

解释一下,condition继承自group模块,就说明了它本身是支持插入多个function node的。function condition模块,又继承自condition,是具体的实现模块。

tips:图中虚线框模块(如 group),表示不可执行部分;实线框模块(如 region),表示可执行部分;点虚线框模块,表示可能执行部分(如 function condition 中的 function node-1

同时,function condition还和param模块相连,表名了它也可以获取和修改param模块中的内容——这就是我为什么让你先看一下上一篇文章的原因,哈哈。

具体实现

1、不依赖当前pipeline:

class MyCondition : public GCondition {
public:
    int choose () override {
        return 1;
    }
};
void tutorial_condition() {
    /* 代码参考CGraph中/tutorial/T06-Condition.cpp */
    CSTATUS status = STATUS_OK;
    GPipelinePtr pipeline = GPipelineFactory::create();
    GElementPtr a, b_condition, c, d_condition = nullptr;

    b_condition = pipeline->createGGroup<MyCondition>({
        pipeline->createGNode<MyNode1>(GNodeInfo("conditionNodeB0", 1)),
        pipeline->createGNode<MyNode2>(GNodeInfo("conditionNodeB1", 1)),
        pipeline->createGNode<MyNode1>(GNodeInfo("conditionNodeB2", 1))
    });    // 生成 b_condition。执行的时候,根据choose()的返回值,在B0,B1,B2中选择一个执行

    d_condition = pipeline->createGGroup<MyParamCondition>({
        pipeline->createGNode<MyNode1>(GNodeInfo("paramConditionNodeD0", 1)),
        pipeline->createGNode<MyNode1>(GNodeInfo("paramConditionNodeD1", 1)),
        pipeline->createGNode<MyNode1>(GNodeInfo("paramConditionNodeD2", 1))
    });

    if (nullptr == b_condition || nullptr == d_condition) {
        return;
    }

    status = pipeline->registerGElement<MyWriteParamNode>(&a, {}, "writeNodeA", 1);
    status = pipeline->registerGElement<MyCondition>(&b_condition, {a}, "conditionB", 1);
    status = pipeline->registerGElement<MyReadParamNode>(&c, {b_condition}, "readNodeC", 1);
    status = pipeline->registerGElement<MyParamCondition>(&d_condition, {c}, "conditionD", 1);

    status = pipeline->init();

    for (int i = 0; i < 3; i++) {
        status = pipeline->run();
        std::cout << "[CGraph] tutorial_condition, loop : " << i + 1 << ", and run status = " << status << std::endl;
    }

    status = pipeline->deinit();

    GPipelineFactory::destroy(pipeline);
}

我们先来看一下不依赖当前pipeline的例子。上面的例子简单吧——自己实现了一个类(MyCondition),继承自GCondition,并且实现了父类的choose方法(纯虚方法)。它的意思就是,每次执行到这里的时候,仅执行MyCondition类中的第1个(从0开始计算,专业不)算子。

用上面那段代码来描述,就是在b_condition这个算子中,包含了conditionNodeB0/conditionNodeB1/conditionNodeB2 三个算子,注册进入pipeline后,每次执行到这里的时候,都会仅执行conditionNodeB1,因为choose()方法返回值固定是1啊。

嗯,这个例子好像也没啥实际的使用场景哈。这样吧,你可以这样想,注册6个节点,然后choose方法里,return rand() % 6,这样就当掷骰子了哈。

d9017b70e8a09cba5a7768d135725fa.jpg

2、 依赖当前pipeline:

再看一个跟pipeline内容有关的哈。

class MyParamCondition : public GCondition {

public:
    int choose () override {
        MyParam* myParam = this->getGParam<MyParam>("param1");
        if (nullptr == myParam) {
            return GROUP_LAST_ELEMENT_INDEX;
        }

        int cnt = myParam->iCount;
        return cnt;
    }
};

MyParamCondition这个类也继承自GCondition类。与刚才不同的是,这个类的choose()函数中通过getGParam方法,获取了pipeline中参数名为"param1"的"myParam"类型的参数,根据其中的iCount值,来判断需要执行第几个算子。如果pipeline中,没有这个参数的话,则会默认执行当前condtion中的最后一个算子——当然了,这个是属于自定义逻辑,你也可以让它执行第一个算子啊。

这样看来,上游的算子就可以通过修改特定参数的值,来控制下游condition模块的执行逻辑了。换句话说,就是condition模块的执行逻辑,可以根据上游逻辑来决定。

需要强调的是,通过这种方式获取参数,同样需要考虑与上下游链路的情况。有并发写入的情况下,还是要做好参数的加锁保护的。

3、依赖pipeline历史执行情况:

其实,理解了上面一种情况,这种情况也就比较好理解了。之前的文章中说过,参数是可以在pipeline不同的执行轮数中传递的。可以设定,当前pipeline执行结束后,是否将参数中特定值进行复位。

依赖pipeline的历史运行情况,只需要结合param不复位的逻辑,就可以很轻松实现了。这里不做过多说明。反正懂的都懂,不懂的可以直接加我微信鸭,哈哈。

一些说明

除了上面的使用方法之外,再简单讲一些说明吧。

比如,想知道当前condition模块中有多少个算子,可以通过getRange()来获取。这个功能在开发的过程中可能会被用到。

condition模块最终也继承自element模块,这个说明了它自身也可以和其他任何模块随机组合,比如 region中加入condition,又或者condition中加入cluster模块。

还有一点,就是现在的设定是,如果当前condition中有n个算子,而choose方法返回的值大于n的话,则不执行任何一个算子,且没有任何异常提示。直接执行下面的逻辑。至于说为什么,主要原因是:我觉得这样好,嘿嘿。


写在最后

我们在日常开发的过程中,除了ctrl+c/v之外,最常用的其实就是if/else。这里主要跟大家描述了CGraph中条件判断逻辑,配合上参数传递和循环执行的逻辑,可以实现非常多的功能。你想啊,平日里我们写的程序,其实不就是for/if/else的各种组合么。而CGraph在做的,无非就是把这些逻辑,移植到一个图框架中来了。

在下一章中,我们会给大家介绍一下,CGraph在处理需要并行的任务的时候,是如何进行线程调度优化的。顺便提一句,CGraph自身也可以被当做是一个简单易用且性能很好的线程池来使用哦。

如果您对这些内容感兴趣,也很欢迎加入我们,一起去构建和优化CGraph,或者是基于CGraph框架进行一些属于自己的功能开发。再或者,给我们提点意见或者bug那也是极好的。还是那句话,Build together, power another.

 					[2021.07.25 by Chunel]

个人信息

微信: ChunelFeng
邮箱: chunel@foxmail.com
个人网站:www.chunel.cn
github地址: https://github.com/ChunelFeng