目录
概述
基本使用
执行器类型
回调组
调度语义
Outlook
更多信息
概述
在 ROS 2 中,执行管理由执行器处理。执行器使用底层操作系统的一个或多个线程来调用订阅、定时器、服务服务器、操作服务器等的回调,以处理传入的消息和事件。显式的 Executor 类(在 rclcpp 中的 executor.hpp,在 rclpy 中的 executors.py,或在 rclc 中的 executor.h)比 ROS 1 中的旋转机制提供了更多的执行管理控制,尽管基本 API 非常相似。
在以下内容中,我们重点介绍 C++ 客户端库 rclcpp。
基本用法
在最简单的情况下,主线程通过调用 rclcpp::spin(..)
来处理节点的传入消息和事件,如下所示:
int main(int argc, char* argv[])
{
// Some initialization.
rclcpp::init(argc, argv);
...
// Instantiate a node.
rclcpp::Node::SharedPtr node = ...
// Run the executor.
rclcpp::spin(node);
// Shutdown and exit.
...
return 0;
}
对 spin(node)
的调用基本上扩展为单线程执行器的实例化和调用,这是最简单的执行器:
rclcpp::executors::SingleThreadedExecutor executor;
executor.add_node(node);
executor.spin();
通过调用执行器实例的 spin()
,当前线程开始查询 rcl 和中间件层的传入消息和其他事件,并调用相应的回调函数,直到节点关闭。为了不抵消中间件的 QoS 设置,传入消息不会存储在客户端库层的队列中,而是保存在中间件中,直到由回调函数处理。(这是与 ROS 1 的一个关键区别。)等待集用于通知执行器中间件层上的可用消息,每个队列有一个二进制标志。等待集还用于检测计时器何时到期。
单线程执行器也被容器进程用于组件 https://docs.ros.org/en/jazzy/Concepts/Intermediate/About-Composition.html ,即在所有没有显式主函数的情况下创建和执行节点。
执行器类型
目前,rclcpp 提供三种执行器类型,均派生自一个共享的父类:
多线程执行器创建可配置数量的线程,以允许并行处理多个消息或事件。静态单线程执行器优化了扫描节点结构(例如订阅、计时器、服务服务器、操作服务器等)的运行成本。它仅在添加节点时执行一次此扫描,而其他两个执行器会定期扫描此类更改。因此,静态单线程执行器应仅与在初始化期间创建所有订阅、计时器等的节点一起使用。
所有三个执行器都可以通过为每个节点调用 add_node(..)
来与多个节点一起使用。
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();
在上面的例子中,静态单线程执行器的一个线程用于同时服务三个节点。对于多线程执行器,实际的并行性取决于回调组。
回调组
ROS 2 允许将节点的回调组织成组。在 rclcpp 中,可以通过 Node 类的 create_callback_group
函数创建这样的回调组。在 rclpy 中,通过调用特定回调组类型的构造函数来完成相同的操作。回调组必须在节点的整个执行过程中存储(例如,作为类成员),否则执行器将无法触发回调。然后,可以在创建订阅、定时器等时指定此回调组 - 例如通过订阅选项:
C++:
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:
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
进行查询。
有两种类型的回调组,类型必须在实例化时指定:
互斥:此组的回调函数不能并行执行。
可重入:此组的回调可以并行执行。
不同回调组的回调可能始终并行执行。多线程执行器使用其线程作为池,根据这些条件尽可能并行处理尽可能多的回调。有关如何有效使用回调组的提示,请参阅使用回调组 https://docs.ros.org/en/jazzy/How-To-Guides/Using-callback-groups.html 。
rclcpp 中的 Executor 基类也具有 add_callback_group(..)
功能,它允许将回调组分配给不同的 Executor。通过使用操作系统调度程序配置底层线程,可以优先处理特定的回调。例如,控制循环的订阅和定时器可以优先于节点的所有其他订阅和标准服务。examples_rclcpp_cbg_executor 包 https://github.com/ros2/examples/tree/jazzy/rclcpp/executors/cbg_executor 提供了这种机制的演示。
调度语义
如果回调的处理时间比消息和事件发生的周期短,执行器基本上会按 FIFO 顺序处理它们。然而,如果某些回调的处理时间较长,消息和事件将会在堆栈的较低层排队。等待集机制仅向执行器报告有关这些队列的非常少的信息。具体来说,它只报告某个主题是否有消息。执行器使用此信息以轮询方式处理消息(包括服务和操作)——但不是按 FIFO 顺序。以下流程图可视化了这种调度语义。
这种语义首次在 Casini 等人在 ECRTS 2019 上的一篇论文 https://drops.dagstuhl.de/opus/volltexte/2019/10743/pdf/LIPIcs-ECRTS-2019-6.pdf 中描述。(注意:该论文还解释了计时器事件优先于所有其他消息。这种优先级在 Eloquent 中被移除 https://github.com/ros2/rclcpp/pull/841 。)
Outlook
虽然 rclcpp 的三个执行器适用于大多数应用程序,但它们存在一些问题,使其不适合实时应用程序,这些应用程序需要明确定义的执行时间、确定性和对执行顺序的自定义控制。以下是其中一些问题的摘要:
复杂且混合的调度语义。理想情况下,您需要定义良好的调度语义以执行正式的时间分析。
回调可能会遭受优先级倒置。较高优先级的回调可能会被较低优先级的回调阻塞。
对回调执行顺序没有明确的控制。
没有内置控制以触发特定主题。
此外,执行器在 CPU 和内存使用方面的开销相当大。静态单线程执行器大大减少了这种开销,但对于某些应用程序来说,这可能还不够。
这些问题已通过以下开发部分解决:
rclcpp WaitSet https://github.com/ros2/rclcpp/blob/jazzy/rclcpp/include/rclcpp/wait_set.hpp : rclcpp 的
WaitSet
类允许直接等待订阅、定时器、服务服务器、操作服务器等,而不是使用执行器。它可用于实现确定性的用户定义处理序列,可能会一起处理来自不同订阅的多个消息。examples_rclcpp_wait_set 包 https://github.com/ros2/examples/tree/jazzy/rclcpp/wait_set 提供了几个使用此用户级等待集机制的示例。rclc 执行器 https://github.com/ros2/rclc/blob/master/rclc/include/rclc/executor.h :这个来自 C 客户端库 rclc 的执行器,为 micro-ROS 开发,赋予用户对回调执行顺序的细粒度控制,并允许自定义触发条件来激活回调。此外,它还实现了逻辑执行时间(LET)语义的理念。
更多信息
Michael Pöhnl 等人:“ROS 2 执行器:如何使其高效、实时和确定性?” https://www.apex.ai/roscon-21 。ROS World 2021 研讨会。虚拟活动。2021 年 10 月 19 日。
拉尔夫·兰格:“使用 ROS 2 的高级执行管理” https://www.youtube.com/watch?v=Sz-nllmtcc8&t=109s 。ROS 工业会议。虚拟活动。2020 年 12 月 16 日。
丹尼尔·卡西尼,托比亚斯·布拉斯,英戈·吕特克博勒和比约恩·勃兰登堡:“基于预留调度的 ROS 2 处理链响应时间分析” https://drops.dagstuhl.de/opus/volltexte/2019/10743/pdf/LIPIcs-ECRTS-2019-6.pdf ,第 31 届 ECRTS 2019 会议论文集,德国斯图加特,2019 年 7 月。