apollo介绍之Cyber框架(三)
写在之前,之前的分析都是一些源码级别的分析,发现一开始就深入源码,很容易陷进去,特别是模块非常多的情况,需要看很多遍才能理解清楚。
要写出更容易理解的文档,需要的不是事无巨细的分析代码,更主要的是能够把复杂的东西抽象出来,变为简单的东西。一个很简答的例子是画函数调用流程图很简单,但是要把流程图转换成框图却很难。
数据处理流程
我们先看下cyber中整个的数据处理流程,通过理解数据流程中各个模块如何工作,来搞清楚每个模块的作用,然后我们再接着分析具体的模块。
如上图所示,cyber的数据流程可以分为50个过程。
- Node节点中的Writer往通道里面写数据。
- 通道中的Transmitter发布消息,通道中的Receiver订阅消息。
- Receiver接收到消息之后,触发回调,触发DataDispather进行消息分发。
- DataDispather接收到消息后,把消息放入CacheBuffer,并且触发Notifier,通知对应的DataVisitor处理消息。
- DataVisitor把数据从CacheBuffer中读出,并且进行融合,然后通过notifier_唤醒对应的协程。
- 协程执行对应的注册回调函数,进行数据处理,处理完成之后接着进入睡眠状态。
对数据流程有整体的认识之后,下面我们在分析具体的每个模块,我们还是按照功能划分。
整体介绍
首先我们对cyber中各个模块做一个简单的介绍,之后再接着分析。实际上我们只要搞清楚了下面一些概念之间的关系,就基本上理解清楚了整个Cyber的数据流程。
1.Component和Node的关系
Component是cyber中封装好的数据处理流程,对用户来说,对应自动驾驶中的Planning Component, Perception Component等,目的是帮助我们更方便的订阅和处理消息。实际上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,概念上对应ROS的消息通道,每个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中注册的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中设置进程的优先级为实时轮转,先保障进程的优先级最高,然后内部再通过协程实现对应的调度策略。
协程和线程的优缺点这里就不展开了,这里有一个疑问是协程不能被终止,除非协程主动退出,这里先留一个伏笔,后面我们再分析协程的调度问题。
总结
上述就是各个概念之间的关系,上述介绍对理解数据的流程非常有帮助,希望有时间的时候,大家可以画一下对应的数据流程图和关系。