确定性执行,简称GSExecute
一、概念
CyberRT采用协程作为调度的基本单位,将原有的内核态调度,变为用户态调度。系统的线程,在CyberRT眼里,可以看做"CPU",或者说是操作系统之上的RTOS。
Cyber RT计算框架中的调度组件负责管理和协调各个模块的执行和资源分配,以实现高效、可靠的系统调度。调度组件对于实时性要求较高的机器人应用程序尤为重要,它能够根据任务的优先级和资源的可用性,合理地分配处理器时间和其他系统资源,以确保系统的实时性和性能。下面是对Cyber RT计算框架中调度组件的概述:
1.Scheduler(调度器): 调度器是Cyber RT框架中的核心组件,它负责决定各个模块的执行顺序和调度策略。调度器根据模块的优先级、资源需求和调度算法等信息,动态地分配处理器时间和其他系统资源,以满足不同模块的需求。调度器可以采用多种调度策略,如抢占式调度、静态优先级调度和动态优先级调度等,以适应不同的应用场景和需求。
2.Executor(执行器): 执行器是调度器的执行单元,负责实际执行模块的任务。它管理着一组处理器或线程,将任务分配给可用的处理器执行。执行器根据调度器的指令,按照特定的调度策略和算法,调度和执行各个模块的任务。执行器还负责监控任务的执行状态和资源的利用情况,以便及时做出调整和优化。
3.Task(任务): 任务是Cyber RT框架中的最小执行单位,它对应于一个具体的功能或计算任务。每个模块都包含一个或多个任务,任务可以是实时性任务或非实时性任务。调度组件通过对任务的调度和执行,实现了模块之间的协同工作和资源管理。
4.Resource Management(资源管理): 资源管理是调度组件的重要功能之一,它负责管理和分配系统资源,包括处理器时间、内存、通信带宽等。资源管理器根据任务的需求和系统的可用资源,进行动态分配和调度,以保证每个任务都能够在合适的时间和资源下运行。
1.1 调度
对线程设置优先级,实时任务和非实时任务的流量分配实际还是依赖cpu
- group:由用户设定的一个线程组,一个group中的所有线程共享任务队列,不同group之间存在隔离
- process:一个process为一个线程,多个process构成一个group。在process中完成协程的上下文切换以及获取下一个要运行的协程
- croutine:协程的概念,一个group共享协程池(任务队列),其中任务队列按照优先级进行分配
- task:用户的任务函数,对应一个croutine
1.1.1 SchedulerClassic
多个process从同一个任务队列里分别取任务运行
G代表协程,P代表执行器process,M代表线程
1.1.2 SchedulerChoreography
与Classic的区别在于,Choreography可以将task和线程进行绑定,即task的存在可知的先后运行关系,即每一个process的任务队列不共享
二、设计
2.1、架构设计
架构设计分为5层
- 基础层,包括对象池、队列等类似GSExecute中的系统封装层
- 通信层,包括服务发现等,类似DDS
- 数据层/融合层,将算法和数据结合起来,类似engine中将DDS的数据取出放入用户的modlue中
- 计算层,包括调度和任务
- 用户接口层,见2.2的3
数据流转
- Node节点中的Write往通道里面写数据
- 通道中的Transmitter发布消息,通道中的Receiver接收消息
- Receiver接收到消息之后,触发回调,触发DataDispather进行消息分发
- DataDispather接收到消息之后,把消息放入CacheBuffer中,并且触发Notifier,通知对应的DataVisitor处理消息
- DataVisitor把数据从CacheBuffer中读出,并且进行融合,然后通过notifier_唤醒对应的协程
- 协程执行对应的注册回调函数,进行数据处理,处理完成之后进入睡眠状态
2.2
- 节点之间的通信采用订阅者模式,实现基于protobuf
- 参考协程(Coroutine)的概念,Cyber RT 实现了 Coroutine 来优化线程使用和系统资源分配,将调度、任务从内核空间放到了用户空间,在原生的thread上加了一层协程(Coroutine),Cyber RT主要调度的就是协程。
- Cyber RT为开发者提供了Component 类,开发者的算法业务模块只需要继承该类,实现其中的 Proc 接口即可。该接口类似于 ROS 中的 Callback,消息通过参数的方式传递,用户只要在Proc中实现算法、消息处理相关的逻辑。Cyber RT 也基于协程,为开发者提供了并行计算相关的接口。
- CyberRT的处理流水线中,一个算法模块(感知、决策、执行)一般对应一个Component
- 用户在算法结果调用WaitForShutdown,WaitForShutdown实现如下。
inline bool IsShutdown() {
return GetState() == STATE_SHUTTING_DOWN || GetState() == STATE_SHUTDOWN;
}
inline void WaitForShutdown() {
while (!IsShutdown()) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
}
2.3、系统层
- 针对协程和线程统一了接口,如果是线程就调用系统层,否则就调用CRouteine中实现的方式(例如yiled、sleep_for等函数)
- 针对任务的选择实现了类似内核的20个优先级的队列,通过从高优先级到低优先级的遍历,选出一个ready的task
- 数据竞争方面对每个group加不同的锁,只涉及到组和组之间的竞争
三、对比
- CyberRT 主要用于协程之间的调度,GSExecute用于进程
- CyberRT 因为采用的是协程,所以可以等到一个task主动放弃才会去调用另外一个task
- CyberRT 类似engine提供了数据触发和时间触发
- CyberRT 调度策略由用户设置,设置为缺省时由CyberRT 提供默认调度
四、优势
- 协程的切换无需内核态和用户态之间的切换,且上下文切换比线程开销小
- task中可以主动调用cyberRT提供的阻塞或yield接口
- 用同步的逻辑来实现异步(我们的思路也是)。针对事件只有当数据到了,才会将对应协程的状态设置为ready
- 线程间定义了一系列的等待和通知策略
五、不足
- CyberRT协程中没有任务抢占,如果process不够,可能会导致低优先级饥饿现象
- CyberRT在配置文件中并无关于时间的设置,但是在实际运行时会记录时间并形成日志,猜想运行时只关注任务的先后(PS:GPT说可以设置任务超时,但是在构造函数中并没有这个)
- 需要显式地使用Cyber RT的接口才能纳入其调度。祼起的原生线程不受控制。理论上如果起个原生线程而且设个很高的优先级可能会打乱已有编排,破坏确定性。
- 一旦一个协程开跑,除非它自己交出控制权,否则没法被其它协程抢占
六、疑问
- CyberRT 如何实现跨机的调度,应该类似于在两个芯片上部署GSExecute的思路
- CyberRT 关于动态调度,即超时如何处理部分
- CyberRT 中的算法模块调用的库创建的线程该如何被调度
六、借鉴
- 参考CyberRT 对协程执行顺序上的调度策略
- CyberRT 对实时通信和非实时通信进行了划分并
- CyberRT 根据算法模块的不同进行分配
七、参考
- https://blog.youkuaiyun.com/qq_25762163/article/details/103591766
- https://blog.youkuaiyun.com/zhizhengguan/article/details/129042150
- https://zhuanlan.zhihu.com/p/121042548
- https://blog.youkuaiyun.com/jinzhuojun/article/details/104087518
- https://blog.youkuaiyun.com/jinzhuojun/article/details/86760743
- https://apollo.baidu.com/community/article/1106