☰
Apollo Cyber RT入门:从ROS视角理解实时通信框架
2026/10/4 12:12:04 网站建设 项目流程

2020 年我第一次打开 Apollo 的源码仓库,第一反应是:这哪里是一个自动驾驶项目,这分明是一座代码迷宫。模块多得数不过来,编译动辄几个小时,启动脚本一把一把的,里面还藏着一个叫 Cyber RT 的东西,名字很唬人,文档却很稀薄。后来我把 ROS 的东西全部忘掉,重新按 Cyber RT 的套路思考,才慢慢理出一条线来。

这篇笔记专门写给两类人:一类是刚接触 Apollo、想搞懂 Cyber RT 到底是干什么的初学者;另一类是像我一样从 ROS 转过来的老机器人开发者,想把 ROS 里的 Topic、Service、Node 这些熟悉的概念平移到 Cyber RT 里。我尽量用刚踩完坑的人的角度来讲,不堆源码,只讲清楚“为什么是它”、“它到底做了什么”,以及在切到 Cyber RT 时,哪几个地方最容易被 ROS 的惯性思维带沟里。

在正式拆之前先点一下题:Cyber RT 是百度 Apollo 在自动驾驶场景下自研的实时通信框架,负责整个系统里模块之间的数据交换、消息调度、任务执行和资源管理。你可以把它理解为 ROS 的 Node 通信机制,加上一个确定性调度器,再加上一块共享内存,最后再解决掉 ROS 里的序列化和进程调度开销问题。

1. 先搞清楚 Cyber RT 在 Apollo 里的位置

1.1 Apollo 这个大系统,到底需要 Cyber RT 解决什么

Apollo 是一个完整的自动驾驶软件栈,里面有感知、定位、预测、规划、控制、高精地图、人机交互等几十个模块。这些模块之间的关系如果画成一张图,会相当复杂:感知模块要出障碍物列表,预测模块要基于障碍物列表输出预测轨迹,规划模块要根据预测结果和地图信息生成轨迹,控制模块再执行轨迹。

问题的关键在于,这些模块不是简单地“按顺序调用”,而是在不同频率、不同数据源、不同延迟要求下并行运行的。举例来说,相机感知输出的是 30 帧每秒的障碍物信息,激光雷达感知输出的是 10 帧每秒的点云检测结果,而控制模块需要以 100 Hz 的频率向底盘发送指令。这些模块同时跑在一台工控机上,数据要要快速流转起来,不能出现某一帧数据因为等待而超时,更不能因为某个模块阻塞就把整条链路卡死。

传统 ROS 在这种场景下会遇到几个非常现实的问题。第一,roscore 是所有节点的中心枢纽,一旦它挂了,整个系统全挂;第二,频繁的序列化和反序列化会在大吞吐量(点云、图像)下带来明显 CPU 开销;第三,ROS 默认的 TCPROS 传输走网络协议栈,即便是本机通信也要过一遍 socket,延迟高而且不稳定;第四,ROS 里的回调执行依赖单一进程内的多线程调度,节点启动顺序不对、频率不匹配时经常丢消息。Autoware 等开源方案做过不少这方向的尝试,但大多还是在 ROS 生态里打转,实时性上不去。

Cyber RT 就是冲着这些痛点去的。它把通信、调度、数据缓存三件事统一到了一个框架里,不再强行区分“网络通信层”和“业务逻辑层”。每个模块被抽象成 Component,订阅固定的 Channel,当数据到达时由内部调度器唤醒对应的协程来执行,而不是启动一个阻塞的线程。这个设计从根上决定了 Cyber RT 比 ROS 更适合无人车这种对延迟和确定性要求极高的场景。

1.2 为什么不能用 ROS 直接改,非要自研一套

每次讲到这,都会有人问:ROS 1 不行那 ROS 2 呢?ROS 2 把通信中间件换成了 DDS,也解决了实时性、分布式、安全性这些 ROS 1 的历史问题,而且社区更大、生态更完整。那 Apollo 为什么还要自己造轮子?

一个重要的原因是 DDS 的配置和学习成本实在高得离谱。ROS 2 本身没有绑定特定 DDS 实现,但不同的 DDS 厂商(Fast DDS、Cyclone DDS、RTI Connext、Eclipse Zenoh 等)在 QoS 策略、共享内存支持、跨平台能力上差别巨大,配置复杂到甚至需要专门的 DDS XML 文件。对于 Apollo 这种发布到量产车上的软件栈,团队必须保证通信层完全可控——延迟曲线、内存占用、调度行为都要能精确测量,出了问题还要能快速定位。把通信底座建立在自己完全掌控的代码上,虽然前期投入大,但后期在性能优化和问题排查上的收益是巨大的。

还有一个现实原因是自动驾驶的数据量太大。一个 64 线激光雷达的点云,一帧大约 4 MB,如果每个 10 Hz 的模块间都用 socket 传一遍,带宽和延迟都吃不消。Cyber RT 对大数据量通道默认走 SharedMemory 传输,消息只拷贝一份进共享内存区,读端直接拿指针,性能非常可观。这种针对自动驾驶场景做深度定制的方案,在通用机器人框架里是做不到的。

当然这不意味着 ROS 专家在 Cyber RT 面前会从零开始,恰恰相反,Cyber RT 的很多核心概念几乎就是 ROS 换了个马甲。你懂 ROS,就手握一张高频词汇对照表,这就是下一篇笔记要讲的。

2. Cyber RT 的核心概念,和 ROS 一个一个对照

2.1 节点(Node)、Writer 和 Reader:对应 ROS 的 Publisher / Subscriber

先说最基础的。在 ROS 里,一个节点(Node)是一个独立的计算单元,通过ros::Publisher往 Topic 上发布消息,通过ros::Subscriber订阅消息。而在 Cyber RT 里,最小的对象同样叫 Node,不过它的发布订阅接口变成了 Writer 和 Reader。

一个典型的 Cyber RT Node 长这样:创建 Node,用CreateWriter<MessageT>(channel_name, qos)得到一个 Writer,用CreateReader<MessageT>(channel_name, callback)得到一个 Reader。Writer 负责往指定 Channel 上写消息,Reader 则在消息到达时被框架回调。

从 API 使用上看,Cyber RT 的 Node 比 ROS 的 NodeHandle 更直观,因为 Reader 创建时要求传入回调函数,而这个回调会真正在数据到达时被调度器调度,而不是在一个内部 event loop 里排队。写代码的时候你不需要主动调spin(),Cyber RT 的调度器会在后台统一管理所有回调的触发时机。

重要区分:ROS 里如果你用完 spy 之后不 spin,回调永远不执行;在 Cyber RT 里,只要你创建了 Reader,并在进程启动时调用了cyber::Init,框架会默认调度所有回调,不写 spin 也没关系。初学的时候经常忽略这一点,导致总感觉回调没被触发。

2.2 通道(Channel):心跳频率和数据方向都刻在里面了

讲 Channel 之前必须强调一个常见的认知偏差:如果用 Topic 的概念去套 Channel,会丢掉一个关键信息——Channel 不仅定义了“数据流向哪里”,还隐式定义了“数据的组织方式”。

在 ROS 里,/odom和/cmd_vel这种 Topic 名称本身是开放约定,谁都能往上面发,谁都能订阅。在 Cyber RT 中,Channel 的语义有些不同,它更像一个“强类型数据总线”。每个 Channel 关联一种消息类型(通常由 Protobuf 定义),发布端和订阅端必须匹配同一消息类型。如果你往/apollo/localization/pose这个 Channel 里塞了一个和定义的 Pose 类型不匹配的消息,运行时会被拒绝。

这也解释了为什么 Apollo 里所有 Channel 的命名都像被统一规范过一样,比如/apollo/perception/obstacles、/apollo/planning/trajectory、/apollo/canbus/chassis。因为这一层强类型约束在系统初期建立得越早,后面调试的成本越低。你会遇到的第一个大坑也在这——编译能过,但两个模块之间 Channel 对不上,或者类型对不上,运行起来就是黑屏。

2.3 服务(Service)与参数(Parameter):几乎没有 ROS 味道的通信三件套之一

ROS 的 Service 长期是一个比较低级的话题,因为它主要用来做同步请求/响应,在复杂机器人系统里用得不如 Topic 多。Cyber RT 也有 Service,接口上长得跟 ROS 几乎一样:CreateService<Req, Res>(service_name, handler)、CreateClient<Req, Res>(service_name, response_handler)。

但这里有个让你爽到的地方:Cyber RT 的 Service 底层调用可以走共享内存。在小数据量的请求/响应场景下,延迟可以被压到非常低,不像是跨进程远程调用,更像是进程内部的函数调用。而 ROS 1 的 service 是走 XML-RPC 的,响应慢得让人怀疑人生。我第一次在 Cyber RT 里连续调用一个 service 1000 次测延迟的时候,得出的数字比 ROS 1 低了两个数量级。

Parameter 这块 Cyber RT 也照搬了 ROS 的思路,提供一个全局参数服务,可以通过/param服务来读取和设置参数。不过在实际 Apollo 项目里,参数的配置更多依赖配置文件(.conf、.pb.txt)而非在线参数服务,所以如果你是从 ROS 过来的,初期不用在这上面花太多精力——先记住“有这个东西”就行,真用到再翻。

2.4 组件(Component)与协程调度(Coroutine):ROS 里没有的硬核机制

如果只停留在“Cyber RT 约等于 ROS 换了名字”,那就大错特错了。Cyber RT 里最核心、最值得花时间理解的,是 Component 机制和基于协程的调度器。

Component 简单理解就是“把某个功能做成一个可以被框架自动加载和调度的对象”。你在 Cyber RT 里开发感知模块时,不需要自己写main()函数,不需要自己建线程跑 while 循环,而是写一个继承cyber::Component<MessageT>的类,实现Init()和Proc()两个方法。框架负责创建组件、订阅 Channel、在数据到达时自动调用Proc()。

而 Proc 的执行不是在一个固定线程池里轮流分配的,而是由 Cyber RT 的协程调度器来决定。协程(Coroutine)你可以通俗理解为“轻量级线程”,它可以在用户态自由挂起和恢复,不需要操作系统线程切换的开销。Cyber RT 里有一个全局协程池,每个 Component 的回调会被封装成一个协程任务,调度器按照优先级、依赖关系和数据到达顺序来调度它们。

为什么要这么设计?因为自动驾驶的模块间存在天然的依赖关系:规划模块必须在感知结果到达之后才能运行,但感知结果到达的时间又不是固定周期的,可能早几毫秒、晚几毫秒。如果每个模块都用自己的线程死等,整个系统会浪费大量线程在阻塞等待上,还会因为线程切换产生不可控的延迟抖动。协程调度器可以“等到数据到了才真正执行”,没有数据时就挂起,CPU 资源可以得到极致的利用。

我在读 Apollo 源码之前一直以为协程是个很高深的东西,直到真的在 ComponentProc()里打日志对比时间戳,才发现这条路子确实能把端到端延迟稳定在个位数的毫秒级别。ROS 1 里做同样的事,要么自己手写线程同步,要么靠开源库兜底,复杂度和结果都是两码事。

2.5 共享内存传输(SharedMemory):点云和图像为什么能跑这么快

Cyber RT 的通信默认支持多种传输模式,其中最有价值的是 SharedMemory。在 ROS 1 里传输一张大图或一帧点云,最常干的事就是开一个 2 GB 的 TCP 缓冲区,结果还是时快时慢。因为 TCP 要复制多次:发送端用户态拷进内核态,接收端从内核态拷到用户态,中间还可能有 Nagle 算法引起的延迟累积。

Cyber RT 的 SharedMemory 传输则简单粗暴:发布端把消息序列化到一块共享内存映射区,接收端直接在这块区域上反序列化或直接拿内存指针,完全不经过内核网络栈。这里有个小细节:不同的“传输类”是通过消息大小自动区分的,小消息默认走 Intramessage,大大消息走 SharedMemory。实测当 Channel 传输的数据超过几 KB,SharedMemory 模式的优势就开始显现,传输延迟和 CPU 占用都会明显低于同场景的 ROS 1 默认配置。

不过有得必有失,共享内存也引入了新的排查难点。比如崩溃后如果共享内存区没能正确清理,新启的进程可能会读到脏数据。我在调试 Apollo 时碰到过一次偶发的点云错乱,查了一下午发现是因为上一轮误杀进程导致共享内存段里残留了半帧旧数据。这个问题不常见,但知道了有个心理准备,遇到时能少掉一半头发。

3. 从零写一个最小 Cyber RT 程序:发布与订阅

3.1 环境准备,别再卡在装和编译上

很多人的 Apollo 学习止步于 Docker 镜像拉不下来,或者 bazel 编译一小时直接劝退。这里先给出我实际用的一套环境方案:Ubuntu 20.04 或 22.04,装好 Docker 和 NVIDIA Container Toolkit,然后用 Apollo 官方提供的 dev 容器环境,镜像里把 Cyber RT 以及依赖都带好了。

关于装 Ubuntu 和 ROS 的这段路,网上已经有大把保姆级教程,比如“Windows 安装 Ubuntu + ROS 全流程”那种双系统 / 虚拟机 / WSL 的图文指南,还有鱼香 ROS 的一键安装脚本,把 ROS 环境装好其实难度不大。装完 ROS 之后再来看 Apollo,还得能接受另一套开发习惯:Apollo 的代码编译和运行几乎都在 Docker 容器里完成,依赖 bazel,不是 ROS 的 catkin_make 或 colcon build。

如果你是纯新手,建议不要在自己的物理机上硬刚源码编译,直接用 Apollo 的预构建镜像或 dev 容器,在容器内体验 Cyber RT。等对框架有感觉了,再去看源码。

3.2 定义消息类型:Cyber RT 是一个强类型系统

Cyber RT 里做通信前必须先定义好消息格式,用的不是 ROS 的.msg,而是 Google Protocol Buffers(Protobuf)。打开 Apollo 源码cyber/proto或者各模块的proto目录,到处都是.proto文件。

写一个最小的学生成绩消息类型:

syntax = "proto2"; package apollo.cyber.demo_base_proto; message Student { optional string name = 1; optional uint64 id = 2; optional double height = 3; repeated double scores = 4; }

用optional和repeated这俩关键字是 Proto2 的特点,Apollo 大量沿用 Proto2 风格,和云原生那边普遍用 Proto3 的习惯不太一样。如果之前没写过 Protobuf,建议先花十分钟熟悉optional、repeated、字段编号、包名这些概念,不然后面看着一堆.pb.h会很晕。

3.3 创建 Writer:一个模拟成绩发布器

写一个最朴素的 talker 程序。假设项目在 Apollo 源码里新建一个cyber/demo目录,下面是发布端的核心代码:

#include "cyber/cyber.h" #include "cyber/demo_base_proto/student.pb.h" using apollo::cyber::cyber::demo_base_proto::Student; int main(int argc, char* argv[]) { apollo::cyber::Init(argv[0]); auto talker_node = apollo::cyber::CreateNode("talker"); auto talker = talker_node->CreateWriter<Student>("/apollo/demo/student", 10); uint64_t seq = 0; apollo::cyber::Rate rate(1.0); while (apollo::cyber::OK()) { auto msg = std::make_shared<Student>(); msg->set_name("zhangsan"); msg->set_id(seq++); msg->set_height(1.75); msg->add_scores(89.0); msg->add_scores(95.5); talker->Write(msg); rate.Sleep(); } return 0; }

梳理一遍这里面的关键点:

第一,cyber::Init(argv[0])是必须的,不调用这个,后面所有框架功能都会异常。它负责初始化日志、调度器、全局状态。

第二,CreateWriter的第二个参数是队列深度,类似 ROS 里的 publisher queue size,但这个队列是在 Writer 内部做缓冲的,消息如果接收端来不及消费,不会像 ROS 1 那样直接丢,而是走协程调度器排队(具体行为还和消息优先级和队列长度有关)。

第三,apollo::cyber::Rate和 ROS 里的ros::Rate几乎一模一样,用于控制循环频率,单位是 Hz。

3.4 创建 Reader:一个简单的监听回调

再看接收端:

#include "cyber/cyber.h" #include "cyber/demo_base_proto/student.pb.h" using apollo::cyber::cyber::demo_base_proto::Student; void MessageCallback(const std::shared_ptr<Student>& msg) { AINFO << "name: " << msg->name() << " id: " << msg->id() << " height: " << msg->height(); } int main(int argc, char* argv[]) { apollo::cyber::Init(argv[0]); auto listener_node = apollo::cyber::CreateNode("listener"); auto listener = listener_node->CreateReader<Student>( "/apollo/demo/student", MessageCallback); apollo::cyber::WaitForShutdown(); return 0; }

这段代码里值得注意的有两个地方。一个是不需要手动执行 spin,因为WaitForShutdown()会让主线程挂起,而框架已经把你注册的MessageCallback挂到了listener对应的 Channel 上,数据一到框架自动调度协程回调。另一个是打印日志用的是AINFO,这是 Cyber RT 封装的日志宏,对应 ROS 里的ROS_INFO。

编译和运行方面,在 Apollo 的 dev 容器里,把两个文件放到cyber/demo目录后,在cyber/demo/BUILD里加入对应的 bazel target,然后执行:

bazel build cyber/demo:talker bazel run cyber/demo:talker

另一个终端启动 listener,就能看到打印输出的成绩信息了。这里用bazel build和bazel run不是唯一方式,但对初学者来说最省心,不用手动处理 LD_LIBRARY_PATH 和可执行文件路径。

3.5 数据录制与回放:用 recorder 代替 rosbag

开发中还有一个高频需求是录制和回放消息,对应 ROS 里的 rosbag。Cyber RT 提供了cyber_recorder工具,基本用法和 rosbag 很像:

# 录制所有通道 cyber_recorder record -a # 回放 cyber_recorder play -f 20240801_120000.record

用起来基本没什么心智负担。想单独回放某一路 Channel,可以加-c /apollo/demo/student。在调试 Apollo 感知模块的时候,我经常用这条命令反复回放真实路采点云数据,调试效率比整车路测高太多。碰到偶发问题,录一段数据回来离线复盘,这种流程在 ROS 生态里也要靠 rosbag 完成,两边思路完全一致。

4. 关键差异不能只看表:三个容易踩坑的思维转变

4.1 整表速查:Cyber RT 与 ROS 1 / ROS 2 对照

直接把最核心的映射关系整理成一张表,方便随时查阅。

功能点ROS 1ROS 2Apollo Cyber RT
最小单元NodeNodeComponent / Node
发布端PublisherPublisherWriter
订阅端SubscriberSubscriberReader
数据通道TopicTopicChannel
服务调用Service Server/ClientService Server/ClientService / Client
参数服务Parameter ServerParameters(基于 ROS 2 service)Parameter(基于 service)
消息定义.msg.msg / .idl.proto(Protobuf)
数据传输默认方式TCPROS/Unix socketDDS 内置共享内存等Intra / SharedMemory / Socket
进程发现roscore(中心化)DDS Discovery(分布式)基于拓扑的中心化拓扑管理
执行模型多线程 + 回调队列多线程 + Executor协程池 + 确定性调度器
录制工具rosbagros2 bagcyber_recorder
日志打印ROS_INFORCLCPP_INFOAINFO / ADEBUG / AERROR

这张表的关键信息量其实不在“名字叫什么”,而在“执行模型”这一行。ROS 1 的多线程回调队列,到了 ROS 2 演化成 Executor 模式,但本质上还是系统线程排队执行回调;Cyber RT 则直接用协程,把上下文切换开销降到极低,并把调度决策掌握在自己手里。这是很多从 ROS 2 转过来的人最不适应的一个点。

4.2 思维转变一:你不再需要手动管理生命周期

ROS 1 里每个节点有两个非常折磨人的问题:一是谁是 master,master 挂了怎么拉起;二是节点退出时资源要不要显式清理。到了 ROS 2 因为 DDS 自带 discovery,分布式问题解决了,但节点生命周期依然由用户自己控制,节点退出时如果不做好 QoS 匹配,对端很容易收不到离线事件。

Cyber RT 把这些收拢到了框架层。一个 Component 自身的生命周期由框架统一管理,你只需要实现 Init 和 Proc;节点间的依赖关系由 Channel 和调度器来维持,而不是自己手写“等三秒确保对方已上线”。在实际开发里,这意味着你写模块时不再需要关心“这个节点是否已经起来”,只管往框架注册你要读的 Channel 和写的 Channel 就行。

4.3 思维转变二:数据调度优先级是显式的,不是靠运气

ROS 里如果两个 callback 同时触发,执行顺序取决于线程的调度运气;如果某个 callback 阻塞太久,其他 callback 就会跟着倒霉。自动驾驶这种强实时场景,这种不确定性是致命的。Cyber RT 里,Channel 可以配置优先级,调度器会按照优先级来安排协程执行,高优先级的回调可以先于低优先级的数据处理被调度。

这一点给开发者带来的思维变化很直接:你需要在设计阶段就明确每个数据流的优先级,而不是写完了再等它变成偶发 bug。比如底盘控制指令优先级最高,感知点云和图像处理次之,日志和标定数据可以放低优先级。这样的配置思路在 ROS 里很难精细表达,因为 ROS 本身没有这种全局调度视图;但在 Cyber RT 里,调度器是系统的一等公民,你对运行时的预期可以做到更强。

4.4 思维转变三:消息定义和代码生成是绕不开的一环

ROS 的.msg文件非常友好,用 Python 写两句就能跑通。Cyber RT 的消息定义则强依赖 Protobuf,涉及protoc代码生成、bazel依赖声明、编译产物链接等多个环节。新手经常在这里被绊倒:不是忘记在 BUILD 文件里加 proto 库依赖,就是 proto 文件放在别的包导致 include 路径对不上。

我的经验是,先把一个最小 proto 从定义到编译成功的完整流程走通,后面所有模块的消息都会顺畅很多。别急着看某个大型模块的调用链,先自己加一个 proto message、编译产物、再在 talker / listener 里引用一遍,跑通了,再回到 Apollo 自带的各种通道去分析数据流,心里就会踏实很多。

5. 实战中常见的问题与排查心得

5.1 编译慢、cache 丢失、依赖不匹配

Apollo 用 bazel 构建,第一次全量编译个把小时很正常,后面增量编译会快很多。最容易遇到的是缓存失效问题,比如你改了根目录下某个 proto,导致下游所有依赖都需要重新生成,编译时间一下子回到解放前。建议是尽量收敛 proto 改动,不要频繁动公共消息定义;添加新模块时,尽量独立成包,减少对全局 BUILD 的牵动。

另外在 dev 容器里,bazel的 cache 是放在容器内的,容器一旦删掉重来,所有编译缓存都会丢失,又得等一轮全量编译。我后来习惯把 bazel 的 output base 挂载到宿主机目录,比如:

bazel --output_user_root=/apollo_bazel_cache build //...

这一招能给经常重建容器的人省下大量时间。它是个特别土的方法,但真的管用。

5.2 Cyber RT 进程启动异常、Channel 数据看不到

从 ROS 转过来的朋友,第一反应大概率是用cyber_monitor去看通道数据。这个工具类似rostopic list加rostopic echo,输入cyber_monitor后会列出所有活跃的 Channel,按数字键可以查看消息内容。如果你启动了几个模块但 monitor 里看不到任何 Channel,不要急着怀疑代码,先检查两个东西:一是cyber::Init是否被正确调用,二是容器有没有正确使用宿主机的网络(需要--net=host)。

还有一个非常隐蔽的问题:Cyber RT 同一时刻只能有一个 master 拓扑,多个进程如果各自初始化出了不同的拓扑环境,即使都在同一个 Docker 容器里,也可能互相看不见。常见的原因是某些服务把CYBER_IP或CYBER_DOMAIN_ID给设置乱了。我踩过一次这坑,排查了大半天,最后发现是环境变量没同步,把两个进程的CYBER_IP指到了不同的网卡上。

5.3 共享内存残留与消息“串台”

前面提过共享内存的脏读问题。如果某个发布端进程被强杀,共享内存段没有被正常释放,新启动的进程可能会读到旧数据。怎么排查呢?一个方法是看消息的时间戳,如果读到的数据时间点明显早于当前时间,大概率就是残留数据。更彻底的方法是清掉共享内存段再重启模块:

ipcs -m ipcrm -m <shmid>

用到再临时处理就行,不用写进自动化脚本。但心里有这个概念,比出了问题才乱翻文档强。

5.4 不要做个只听不做的人:用源码和例子打底

网上关于 Cyber RT 的系统性教程很少,最靠谱的学习路径是读源码里的examples。打开 Apollo 仓库,cyber/examples目录下有现成的 talker/listener、service/client、component 示例,是理解框架最好的入门材料。还有一份《Apollo Cyber RT Developer Guide》藏在docs/cyber下,官方文档虽不算特别完善,但把概念和 API 过一遍,配合源码里的例子,基本够用了。

结尾

我个人在实际操作里体会到,Cyber RT 真正难的地方不是 API 本身,而是思维的切换。ROS 给你的是自由度,让你想怎么搭就怎么搭;Cyber RT 给你的是约束,但约束换来的是更强的实时性和确定性。所以从 ROS 过来的人,第一周会非常别扭,第二周会开始适应,第三周基本就能感受到协程调度和共享内存带给你的快感了。

最后再分享一个小技巧:学习阶段别急着把 Apollo 所有模块都跑起来,不是所有算力都够,也不是所有热情都能撑到全量编译结束。单开 Cyber RT,用cyber_recorder回放一段 Apollo 官方提供的 demo 数据,再写一个最小模块订阅你感兴趣的 Channel,打印出来看看,一层一层往里剥。这样做,比每天看一遍源码目录要有效得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询