docs/框架设计/软件核心/CyberRT/Cyber RT 调度机制.md
随着人工智能技术的不断发展,自动驾驶汽车已经开始变为可能。自动驾驶汽车需要同时完成多项任务,包括定位、感知、规划、控制等。如何合理调度编排这些任务,让它们运行得稳定、高效,是我们今天介绍的主要内容。
操作系统进行调度的目的是为了最大化利用系统资源(特别是CPU资源)。调度往往是对有限资源的妥协,本质是为了:效率最大化,兼顾公平。
先看以下的例子,有3个任务,当一个任务执行完成之后,再开始执行另一个任务,3个任务的执行时间分别为10s,6s和4s,我们应该如何编排任务呢?
通过上面的例子可以看出,策略1和策略2的CPU总执行时间不变,但是等待时间变少了。也就是说调度系统帮我们减少了任务的总等待时间。 超市里经常遇到排队的场景,当只买了少量的东西,和买大量东西的人排一队的时候,可能就会遇到等待时间过长的问题。
回到上面的例子,最优的调度策略诞生了,它就是最短时间优先,每次最短时间的任务优先执行,这样可以保证任务总等待时间最短。
可能马上就有人想到了,每次只创建小任务,这样可以保证自己的任务每次都可以插队,被优先执行。但问题来了,这会导致大任务总是得不到执行,一直处于饥饿状态。oh!!! 效率最大化会导致CPU利用的不公平,而我们要兼顾公平。
最短时间优先的策略很棒,但我们忘记了一个重要问题,在任务执行完之前,我们并不知道任务会执行多久!!!策略失效了!!!
别灰心,有新的方法:时间片轮转。交替执行各个任务,每个任务分配一小段CPU时间,时间用尽则退出,让给其它任务使用。
Tips
要支持时间片轮转,操作系统和硬件配合,实现任务抢占。
Tips
实时操作系统不是万能的。很多人可能会问,假如我有一个实时系统,那么是否意味着任务总是可以按时完成,想法很美好,也符合实时操作系统的定义,但资源是有限的,例如你不可能同时吃饭还喝水。 只凭借有限的资源,却能保证完成无限的事情,如果有这样的系统我想它的成就不亚于永动机。因此当程序处理不过来的时候,我们要检查资源是否足够,特别是忙碌的时候。
Cgroup是 Linux 内核的一个特性,用于限制、记录和隔离一组进程的资源使用(CPU、内存、磁盘 I/O、网络等)。Cgroup 具有以下特性:
pthread_setaffinity_np
choreography_conf {
choreography_processor_num: 8
choreography_affinity: "range"
choreography_cpuset: "0-7" # bind CPU cores
choreography_processor_policy: "SCHED_FIFO" # policy: SCHED_OTHER,SCHED_RR,SCHED_FIFO
协程。用户态的线程,由用户控制切换。协程的定义可以参考go语言中的GMP 模型
协程相对线程的优势
Tips
只能自己yeild,最好不要使用sleep,或者io,不然就会主动休眠
为什么引入协程?因为自动驾驶系统涉及到很多消息传输,因此会导致大量的IO消耗,那么如何减少任务切换带来的开销就显得尤为重要。
引入协程可以减少任务切换带来的开销,从而提高程序的效率。在自动驾驶系统中,大量的IO消耗会使得系统性能下降。通过使用协程,可以将系统的IO操作和非IO操作分别处理,减少任务切换的次数,从而提高系统的响应速度和效率。
此外,使用协程还可以简化代码结构,提高代码的可读性和可维护性。在自动驾驶系统中,涉及到大量的数据的处理和传输,如果使用传统的多线程编程方式,将会使得代码结构复杂,难以维护。而使用协程可以将代码结构简化,使得代码更加清晰易懂,从而提高代码的可读性和可维护性。
综上所述,引入协程可以减少任务切换带来的开销,提高系统的响应速度和效率,同时也可以简化代码结构,提高代码的可读性和可维护性。
对应到GMP模型,Cyber的每个任务都被视为一个协程,协程在线程上运行,并且可以设置协程的优先级。协程通过一个多优先级队列来管理,每次从优先级最高的协程开始执行。
有意思的是有些Processor上的任务分配的比较少,有些Processor上的任务可能分配的比较多,这样会导致一些Processor闲置,而另一些则处于饱和状态。为了提高效率,空闲的Processor会从饱和的Processor上偷取一半的任务过来执行,从而提高CPU利用率。
组件是Apollo中的最小执行单元,每个组件对应某一项特定的任务,例如定位、控制。多个组件联合起来可以实现更加复杂的功能,例如感知。Apollo一共有2种类型的组件:消息触发型和定时触发型。
class CommonComponentSample : public Component<Driver> {
public:
bool Init() override;
bool Proc(const std::shared_ptr<Driver>& msg0) override;
};
CYBER_REGISTER_COMPONENT(CommonComponentSample)
如果在程序中想启动新的任务并发处理,可以使用cyber::Async接口,创建的任务会在协程池中由cyber统一调度管理。
class Foo {
public:
void RunOnce() {
auto res = Async(&Foo::Task, this, 10);
EXPECT_EQ(res.get(), 10);
}
uint32_t Task(const uint32_t& input) { return input; }
};
Tips
手动创建的线程并没有设置cgroup,因此最好不要手动创建线程,如果有需要可以通过cyber配置来设置cgroup。