ROS2 Jazzy:执行器

ROS2 中的执行管理由 执行器(Executor) 处理。执行器利用底层操作系统的一个或多个线程,在接收到消息和事件时调用订阅者、定时器、服务服务器、动作服务器的回调函数。显式的Executor类(rclcpp 中的executor.hpp、rclpy 中的executors.py或 rclc 中的executor.h)提供了与ROS1 API 非常相似的,但是比 ROS1 中spin机制更精细的执行管理控制。

以下我们主要关注 C++ 客户端库 rclcpp。

基本用法

最简单的情况是通过调用rclcpp::spin(..),使用主线程处理节点的传入消息和事件,示例如下:

cpp 复制代码
int main(int argc, char* argv[])
{
   // 初始化
   rclcpp::init(argc, argv);
   ...

   // 创建节点
   rclcpp::Node::SharedPtr node = ...

   // 运行执行器
   rclcpp::spin(node);

   // 关闭并退出
   ...
   return 0;
}

调用spin(node)本质上等同于实例化并调用单线程执行器(Single-Threaded Executor),这是最简单的执行器:

cpp 复制代码
rclcpp::executors::SingleThreadedExecutor executor;
executor.add_node(node);
executor.spin();

调用执行器实例的spin()后,当前线程会开始查询 rcl 和中间件层的传入消息和其他事件,并调用相应的回调函数,一直到节点关闭。为了不影响中间件的 QoS 设置,传入消息不会存储在客户端库层的队列中,而是保留在中间件中,直到被回调函数处理(这里是与 ROS1 的关键区别)。执行器通过等待集(wait set) 感知中间件层的可用消息,每个队列对应一个二进制标志位,等待集也用于检测定时器是否到期。

在所有无需显式主函数创建和执行节点的场景中,单线程执行器也用于组件的容器进程。

执行器类型

当前,rclcpp 提供了三种执行器类型,均派生自同一个父类:

其中多线程执行器(Multi-Threaded Executor) 会创建可配置数量的线程,允许并行处理多条消息或事件。 而静态单线程执行器(Static Single-Threaded Executor) 针对节点的订阅、定时器、服务服务器、动作服务器等结构的扫描进行了运行时优化。它仅在添加节点时执行一次扫描,而另外两种执行器则会定期扫描这些变化。因此,静态单线程执行器应该仅仅在节点初始化时创建所有订阅、定时器等资源的场景中使用。

所有三种执行器都可以通过调用 add_node(..) 添加多个节点:

cpp 复制代码
rclcpp::Node::SharedPtr node1 = ...;
rclcpp::Node::SharedPtr node2 = ...;
rclcpp::Node::SharedPtr node3 = ...;

rclcpp::executors::StaticSingleThreadedExecutor executor;
executor.add_node(node1);
executor.add_node(node2);
executor.add_node(node3);
executor.spin();

在上面这个例子中,静态单线程执行器的单个线程用于共同服务三个节点。对于多线程执行器,实际并行性取决于回调组(Callback Groups)。

回调组

ROS2 允许将节点的回调组织成组。在 rclcpp 中,可通过 Node 类的 create_callback_group 函数创建回调组;在 rclpy 中,则通过调用特定回调组类型的构造函数实现。回调组必须在节点的整个执行周期中保持存活(例如作为类成员),否则执行器无法触发回调。创建订阅、定时器等资源时,可通过选项指定回调组,例如:

C++

cpp 复制代码
my_callback_group = create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);

rclcpp::SubscriptionOptions options;
options.callback_group = my_callback_group;

my_subscription = create_subscription<Int32>("/topic", rclcpp::SensorDataQoS(),
                                             callback, options);

Python

python 复制代码
my_callback_group = MutuallyExclusiveCallbackGroup()
my_subscription = self.create_subscription(Int32, "/topic", self.callback, qos_profile=1,
                                           callback_group=my_callback_group)

所有创建时未指定回调组的订阅、定时器等资源,都会被分配到默认回调组。在 rclcpp 中,可通过 NodeBaseInterface::get_default_callback_group() 查询默认回调组;在 rclpy 中则通过 Node.default_callback_group 获取。

回调组有两种类型,需在实例化时指定:

  • 互斥组(Mutually exclusive):该组的回调禁止并行执行。
  • 可重入组(Reentrant):该组的回调允许并行执行。

不同回调组的回调始终可以并行执行。多线程执行器会根据这些规则,利用线程池尽可能并行处理回调。有关高效使用回调组的建议,可以参考《使用回调组》。

rclcpp 的执行器基类还提供 add_callback_group(..) 函数,允许将回调组分配给不同执行器。通过操作系统调度器配置底层线程,可对特定回调设置优先级(例如,控制回路的订阅和定时器可优先于节点的其他订阅和标准服务)examples_rclcpp_cbg_executor(github.com/ros2/exampl...) 软件包提供了该机制的演示。

调度语义

如果回调的处理时间短于消息和事件的产生周期,执行器基本按 先进先出(FIFO) 顺序处理它们。而如果某些回调处理时间较长,消息和事件会在底层栈中排队。等待集(Wait Set)机制仅向执行器报告这些队列的少量信息(例如是否有某个主题的消息)。执行器基于此信息以 轮询(Round-Robin) 而非 FIFO 顺序处理消息、服务和动作。以下流程图展示了该调度语义:

这种语义最早由 Casini 等人在 ECRTS 2019 会议的论文(drops.dagstuhl.de/opus/vollte...) 中描述(注:该论文还提到定时器事件优先级高于其他消息,但这一优先级在 Eloquent 版本中已移除(github.com/ros2/rclcpp...)%25E3%2580%2582 "https://github.com/ros2/rclcpp/pull/841))%E3%80%82")

未来展望

尽管 rclcpp 的三种执行器适用于大多数应用,但仍存在一些不适合实时应用的问题(实时应用需要明确的执行时间、确定性和对执行顺序的自定义控制),主要问题包括:

  • 复杂且混合的调度语义,理想情况下需要明确的调度语义进行形式化时序分析。
  • 回调可能出现优先级反转,高优先级回调可能被低优先级回调阻塞。
  • 无法显式控制回调执行顺序。
  • 缺乏对特定主题触发的内置控制。
  • 执行器在 CPU 和内存使用方面的开销较大,静态单线程执行器虽然大幅减少了开销,但对某些应用可能仍然不足。

下面这两个功能部分解决了这些问题:

  • rclcpp WaitSet :rclcpp 的 WaitSet 类允许不使用执行器,而直接等待订阅、定时器、服务服务器、动作服务器等资源。它可用于实现确定的用户自定义处理序列,甚至同时处理来自不同订阅的多条消息。examples_rclcpp_wait_set(github.com/ros2/exampl...) 软件包提供了多个使用该用户级等待集机制的示例。
  • rclc 执行器:C 客户端库 rclc(为 micro-ROS 开发)的执行器允许用户细粒度控制回调的执行顺序,并支持自定义触发条件激活回调。此外,它实现了逻辑执行时间(LET)语义的思想。

欢迎关注【智践行】,发 【机器人】 获得机器人经典学习资料

相关推荐
Frank_HarmonyOS6 分钟前
Android MVVM(Model-View-ViewModel)架构
android·架构
reddish2 小时前
用大模型“语音指挥”网站运维?MCP + Coze 实现无代码自动化管理实战
人工智能·程序员·架构
♡喜欢做梦2 小时前
企业级大模型解决方案:架构、落地与代码实现
人工智能·ai·架构
麦客奥德彪16 小时前
React native 项目函数式编程的背后-另类的架构InversifyJS 依赖注入(DI)
react native·架构·客户端
曾经的三心草18 小时前
微服务的编程测评系统13-我的竞赛列表-elasticSearch
windows·微服务·架构
久笙&19 小时前
对象存储解决方案:MinIO 的架构与代码实战
数据库·python·架构
L2ncE19 小时前
高并发场景数据与一致性的简单思考
java·后端·架构
一休哥助手19 小时前
Naive RAG:简单而高效的检索增强生成架构解析与实践指南
运维·人工智能·架构
小云数据库服务专线19 小时前
谈谈架构的内容
架构·数据库架构
HyggeBest19 小时前
Golang 并发原语 Sync Cond
后端·架构·go