执行模型
执行模型
我们可以将所有的编程方法分为两类:数据流编程和控制流编程。一些比较常见的系统开发语言都属于控制流编程语言,如 C/C++ 和 Java 等。而近年比较流程的一些框架比如 Apache Flink 和 Tensorflow 等都可以称之为数据流编程(这里的编程语言是泛指的一种思维方式)。
函数式编程(Functional Programming)是比较符合数据流编程范式的一种语言。比较常见的函数式编程有历史悠久的 LISP,还有 Haskell 、 Erlang 以及 OCamel 等。一般情况下,我们可以将数据流编程等同于函数式编程。
Ticos 开发语言属于数据流编程,也符合常规的函数式编程范式。这种开发范式非常适合需要管理一系列设备状态的场景。
程序生命周期
在任何特定时刻,Ticos 程序要么处于执行状态(Transaction),要么处于空闲状态(Idle)。
空闲时,系统会保持稳定状态,各个子设备的状态数据不会有任何主动的变化(比如灯会一直保持亮或者灭,而不会自行开关)。如果空闲状态够长,我们可以选择让电路板进入睡眠状态以提升电池续航时间。时钟信号或者传感器输入等外部信号会使程序从空闲状态切换为执行状态,执行完毕后如果没有新的信号需要处理,则程序又会回到空闲状态。
一个信号使程序进入一个新事务。更新后的值沿着链接向下游流动,并导致它们命中的节点更新。更新节点的过程在 Ticos 中称为求值(Evaluation)。
在对所有受本次执行影响的所有节点进行求值后,执行结束且系统返回空闲状态。
执行规则
在执行过程中遵循如下基本规则:
执行过程中不接受外部信号
任何一个程序在接受信号并开始执行后,不能通过输入新的信号来打断本次执行。如果有新的信号输入会在本次执行结束后再重新运行求值过程。
顺序执行
任何一个节点只有在所有其依赖节点已经被执行求值过程后才会进行求值过程。
参考如下示例:
其中的 add
节点只有在前面的两个分支都求值完毕后才会被执行求值过程。而这两个分支中的的求值顺序则是不确定的,我们不能对其执行顺序有任何假设。他们可能是先后的,也可能是并行的。底层的平台实现可以自行决定最佳的选择。
其中有一条规则是确定的, 就是我们永远不应该基于不完整的输入数据进行求值。这也是同一个输入端口不能有多个输入来源的原因,否则我们无法确定应该使用哪个输入值。
缓存
节点的输出会在值发生变化时被缓冲。换句话说,节点的输出端口会一直保持其最新值,且这些数值在事务之间被传递。因此,如果由于另一个输入值的变化而对某节点重新求值时,该节点仍然可以获取到来自上次求值过程的缓存结果。
我们可以认为每一个输出端口都是一个变量。最新的求值结果都会保持在这个变量中。这是缓存的基本原理。
循环过程
返回到直接或间接上游节点的链接会创建一个循环,也称为图循环。这样的链接打破了之前确定的求值规则,使求值顺序模棱两可:因为循环中的每个节点在最后一回合都依赖于自身的求值结果。
为了解决这个问题,我们添加了一个特殊的 defer
节点,通过该节点打破死锁并通过下一个求值过程的输出链接传递接收到的值。换句话说,Ticos 程序支持循环,但循环路径中必须至少有一个 defer
节点以确定正确的求值顺序。
错误处理
某些节点可能会引发错误,而不是设置值或发出脉冲。错误根据程序流程递归地向下传播并阻止对所有受影响节点的求值过程。请参阅错误处理相关文档中的详细信息。
本文小结
Ticos 程序的生命周期可以看作是在收到外部输入时才运行的被动任务体系。
程序运行过程中的求值过程受到保护,不受突发的外部影响,因为这些外部因素可能会导致节点的求值顺序不明确。