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)语义的思想。

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

相关推荐
conkl2 小时前
嵌入式 Linux 深度解析:架构、原理与工程实践(增强版)
linux·运维·服务器·架构·php·底层·堆栈
●VON7 小时前
重生之我在暑假学习微服务第七天《微服务之服务治理篇》
java·学习·微服务·云原生·nacos·架构·springcloud
贾全7 小时前
Transformer架构全解析:搭建AI的“神经网络大厦“
人工智能·神经网络·ai·语言模型·自然语言处理·架构·transformer
潘锦9 小时前
架构师必备:解决技术问题当从第一性原理开始
架构·cto
kaliarch9 小时前
IaC 管控资源发生属性偏移修正方案
后端·架构·自动化运维
曾经的三心草10 小时前
微服务的编程测评系统9-竞赛新增-竞赛编辑
微服务·架构·状态模式
数据智能老司机10 小时前
DevOps 安全与自动化——理解 DevOps 文化与原则
架构·自动化运维·devops
数据智能老司机10 小时前
DevOps 安全与自动化——开发环境搭建
架构·自动化运维·devops
天下无贼!11 小时前
【自制组件库】从零到一实现属于自己的 Vue3 组件库!!!
前端·javascript·vue.js·ui·架构·scss