最近面试的过程中,发现不少面试官喜欢问Cyber框架的实现原理和特点,并且会结合操作系统的进程、线程和协程的概念,特此总结一下。

Cyber介绍

Cyber RT是一个runtime framework,可以理解为百度针对ROS 1在自动驾驶环境下的一些天生缺陷做的一套自己的框架(很多缺陷在ROS 2中得到了解决但ROS 2迟迟不见稳定版本)。Cyber RT的目标是做到高并发,低延迟以及高吞吐,这些特性都是自动驾驶任务所必须的。

简单来说,cyber是一个分布式收发消息,和调度框架

其架构,如下图所示:

trinitycore 架构 cyber架构_编程语言

  1. Apollo实现了最下层的基础库,比如Lock-Free的对象池,Lock-Free的队列等。这么做的目的一个是提高效率,另一个就是减少依赖
  2. 通信机制(从下往上第2,3层),包括服务发现和Publish-Subscribe通信机制。CyberRT也支持跨进程、跨机通信,上层业务逻辑无需关心,通信层会根据算法模块的部署,自动选择相应通信机制。
  3. 通信层之上的数据缓存/融合层(第4层)。不同算法模块之间需要有一个数据桥梁,数据层起到了这个模块间通信的桥梁的作用。
  4. 再往上是计算模型,包括调度层和任务
  5. 最上面是提供给开发者的接口层

细节介绍

调度

将调度、任务从内核空间放到了用户空间,在原生的thread上加了一层协程(Coroutine),Cyber RT主要调度的就是协程

协程,又称微线程。
实际上协程就是类函数一样的程序组件,你可以在一个线程里面轻松创建数十万个协程,就像数十万次函数调用一样。协程之间可以通过 yield 方式转移执行权,对称(symmetric)、平级地调用对方,而不是像函数那样上下级调用关系。
Cyber RT调度器调度有状态的协程在各个线程上运行。协程不仅切换快,而且调度有着高确定性,不像线程的调度完全依赖操作系统

trinitycore 架构 cyber架构_分布式_02

通信

上层业务逻辑无需关心如何通信,通信层会根据算法模块的部署,自动选择相应通信机制(进程间、线程间、网络间)

trinitycore 架构 cyber架构_trinitycore 架构_03


trinitycore 架构 cyber架构_trinitycore 架构_04

数据处理流程

trinitycore 架构 cyber架构_编程语言_05


如上图所示,cyber的数据流程可以分为6个过程。

  1. Node节点中的Writer往通道里面写数据。
  2. 通道中的Transmitter发布消息,通道中的Receiver订阅消息。
  3. Receiver接收到消息之后,触发回调,触发DataDispather进行消息分发。
  4. DataDispather接收到消息后,把消息放入CacheBuffer,并且触发Notifier,通知对应的DataVisitor处理消息。
  5. DataVisitor把数据从CacheBuffer中读出,并且进行融合,然后通过notifier_唤醒对应的协程。
  6. 协程执行对应的注册回调函数,进行数据处理,处理完成之后接着进入睡眠状态。

通信节点概念

1.Component和Node的关系

Component是cyber中封装好的数据处理流程,Component模块在加载之后会执行"Initialize()"函数,这是个隐藏的初始化过程,对用户不可见。在"Initialize"中,Component会创建一个Node节点,概念上对应ROS的节点,每个Component模块只能有一个Node节点,也就是说每个Component模块有且只能有一个节点,在Node节点中进行消息订阅和发布
2.Node和Reader\Writer的关系

在Node节点中可以创建Reader订阅消息,也可以创建Writer发布消息,每个Node节点中可以创建多个Reader和Writer。
3.Reader和Receiver,Writer和Transmitter,Channel的关系

一个Channel对应一个Topic,每个Topic都是唯一的。而Channel中包括一个发送器(Transmitter)和接收器(Receiver),通过Receiver接收消息,通过Transmitter发送消息。
一个Reader只能订阅一个通道的消息,如果一个Node需要订阅多个通道的消息,需要创建多个Reader。同理一个Writer也只能发布一个通道的消息,如果需要发布多个消息,需要创建多个Writer。
Reader中调用Receiver订阅消息,而Writer通过Transmitter发布消息。
4.Receiver, DataDispatcher和DataVisitor的关系

每一个Receiver接收到消息之后,都会回调中触发DataDispather(发布消息,DataDispather是一个单例,所有的数据分发都在数据分发器中进行,DataDispather会把数据放到对应的缓存中,然后Notify(通知)对应的协程去处理消息
DataVisitor(消息访问器)是一个辅助的类,一个数据处理过程对应一个DataVisitor,通过在DataVisitor中注册Notify(唤醒对应的协程,协程执行绑定的回调函数),并且注册对应的Buffer到DataDispather,这样在DataDispather的时候会通知对应的DataVisitor去唤醒对应的协程。
也就是说DataDispather(消息分发器)发布对应的消息到DataVisitor,DataVisitor(消息访问器)唤醒对应的协程,协程中执行绑定的数据处理回调函数。

5.DataVisitor和Croutine的关系
实际上DataVisitor中的Notify是通过唤醒协程(为了方便理解也可以理解为线程,可以理解为你有一个线程池,通过线程池绑定数据处理函数,数据到来之后就唤醒对应的线程去执行任务),每个协程绑定了一个数据处理函数和一个DataVisitor,数据到达之后,通过DataVisitor中的Notify唤醒对应的协程,执行数据处理回调,执行完成之后协程进入休眠状态。

6.Scheduler, Task和Croutine
通过上述分析,数据处理的过程实际上就是通过协程完成的,每一个协程被称为一个Task,所有的Task(任务)都由Scheduler进行调度。从这里我们可以分析得出实际上Cyber的实时调度由协程去保障,并且可以灵活的通过协程去设置对应的调度策略,当然协程依赖于进程Apollo在linux中设置进程的优先级为实时轮转先保障进程的优先级最高然后内部再通过协程实现对应的调度策略

参考链接

https://www.pianshen.com/article/29641500401/
https://zhuanlan.zhihu.com/p/91322837
https://zhuanlan.zhihu.com/p/115046708