您正在阅读的是旧版本但仍受支持的 ROS 2 文档。 Jazzy.
执行人
概述
ROS 2 中的执行管理由执行器负责。执行器使用底层操作系统的一个或多个线程来调用订阅、定时器、服务服务器、动作服务器等对传入消息和事件的回调。显式 Executor 类(在 executor.hpp 中的 executors.py 或 executor.h rclc)比 ROS 1 中的自旋机制提供了更多的执行管理控制,尽管基本的应用程序接口非常相似。
下面,我们将重点介绍 C++ 客户端库 rclcpp.
基本用途
在最简单的情况下,主线程通过调用 rclcpp::spin(..)
具体如下
int 主要(int 参数, 烧焦* 参数[])
{
// 进行一些初始化。
rclcpp::启动(参数, 参数);
...
// 实例化一个节点。
rclcpp::节点::SharedPtr 网站 = ...
// 运行执行器。
rclcpp::后旋(网站);
// 关闭并退出。
...
返回 0;
}
呼吁 自旋(节点)
基本上扩展为单线程执行器的实例化和调用,这是最简单的执行器:
rclcpp::执行人::单线程执行器 执行人;
执行人.添加节点(网站);
执行人.后旋();
通过调用 自旋()
执行器实例的当前线程开始向 rcl 层和中间件层查询传入消息和其他事件,并调用相应的回调函数,直至节点关闭。为了不影响中间件的 QoS 设置,收到的信息不会存储在客户端库层的队列中,而是保存在中间件中,直到回调函数对其进行处理。(这是 ROS 1 的一个重要区别)。 等待设置 用于向执行器通报中间件层的可用消息,每个队列有一个二进制标志。二进制标志 等待设置 也用于检测计时器何时过期。

单线程执行器还被容器进程用于 部件即在创建和执行节点时没有明确的主函数。
执行人类型
目前,rclcpp 提供了三种 Executor 类型,它们都源自一个共享的父类:
![digraph Flatland { Executor -> SingleThreadedExecutor [dir = back, arrowtail = empty]; Executor -> MultiThreadedExecutor [dir = back, arrowtail = empty]; Executor -> StaticSingleThreadedExecutor [dir = back, arrowtail = empty];Executor [shape=polygon,sides=4]; SingleThreadedExecutor [shape=polygon,sides=4]; MultiThreadedExecutor [shape=polygon,sides=4]; StaticSingleThreadedExecutor [shape=polygon,sides=4]; }](../../_images/graphviz-9e22cb352286aa42db205659c38315ff2f1eb383.png)
"(《世界人权宣言》) 多线程执行器 会创建可配置数量的线程,以便并行处理多个消息或事件。线程 静态单线程执行器 优化了从订阅、计时器、服务服务器、动作服务器等方面扫描节点结构的运行成本。它只在节点添加时执行一次扫描,而其他两个执行器会定期扫描此类变化。因此,静态单线程执行器只能用于在初始化过程中创建所有订阅、定时器等的节点。
通过调用 add_node(..)
每个节点。
rclcpp::节点::SharedPtr 节点1 = ...
rclcpp::节点::SharedPtr 节点2 = ...
rclcpp::节点::SharedPtr 节点3 = ...
rclcpp::执行人::静态单线程执行器 执行人;
执行人.添加节点(节点1);
执行人.添加节点(节点2);
执行人.添加节点(节点3);
执行人.后旋();
在上述示例中,静态单线程执行器的一个线程用于同时为三个节点提供服务。对于多线程执行器,实际并行性取决于回调组。
回调组
ROS 2 允许将节点的回调组织成组。在 rclcpp 中,这样的 回调组 可以通过 创建回调组
函数。在 rclpy 中,同样是调用特定回调组类型的构造函数。回调组必须存储在节点的整个执行过程中(如作为类成员),否则执行器将无法触发回调。然后,可以在创建订阅、定时器等时指定回调组。- 例如通过订阅选项:
我的回调组 = 创建回调组(rclcpp::回调组类型::相互排斥);
rclcpp::订阅选项 选项;
选项.回调组 = 我的回调组;
我的订阅 = 创建订阅<;Int32>;("/topic";, rclcpp::传感器数据QoS(),
回调, 选项);
我的回调组 = 互斥回调组()
我的订阅 = 自我.创建订阅(Int32, "/topic";, 自我.回调, qos_profile=1,
回调组=我的回调组)
所有未指定回调组而创建的订阅、定时器等都会分配给 默认回调组.可以通过 NodeBaseInterface::get_default_callback_group()
和 Node.default_callback_group
在 rclpy 中。
回调组有两种类型,必须在实例化时指定类型:
相互排斥: 该组的回调不得并行执行。
重入 该组的回调可以并行执行。
不同回调组的回调总是可以并行执行。多线程执行器使用线程作为线程池,根据这些条件并行处理尽可能多的回调。有关如何高效使用回调组的提示,请参阅 使用回调组.
rclcpp 中的 Executor 基类还有一个函数 add_callback_group(..)
,可以将回调组分配给不同的执行器。通过使用操作系统调度器配置底层线程,特定回调可优先于其他回调。例如,控制循环的订阅和定时器可优先于节点的所有其他订阅和标准服务。调度程序 examples_rclcpp_cbg_executor 软件包 提供了这一机制的演示。
调度语义
如果回调的处理时间短于消息和事件发生的周期,执行器基本上会按先进先出的顺序处理它们。但是,如果某些回调的处理时间较长,消息和事件就会在堆栈的下层排队。等待集机制向执行器报告的关于这些队列的信息非常少。具体来说,它只报告某个主题是否有任何消息。执行器利用这些信息以循环方式处理报文(包括服务和操作),但不是按先进先出的顺序。下面的流程图直观地展示了这种调度语义。

这种语义首次在 卡西尼等人在 ECRTS 2019 上发表的论文.(注:本文还解释了定时器事件的优先级高于所有其他报文。 Eloquent 取消了这一优先级。)
展望
虽然 rclcpp 的三个执行器能很好地满足大多数应用程序的要求,但也存在一些问题,使它们不适合实时应用程序,因为实时应用程序需要明确定义的执行时间、确定性和对执行顺序的自定义控制。下面是其中一些问题的总结:
复杂和混合的调度语义。理想情况下,你需要定义明确的调度语义来执行正式的时序分析。
回调可能会出现优先级倒置。优先级较高的回调可能会被优先级较低的回调阻塞。
无法明确控制回调执行顺序。
无法对特定主题的触发进行内置控制。
此外,执行器在 CPU 和内存使用方面的开销也相当大。静态单线程执行器大大减少了这种开销,但对于某些应用程序来说,这可能还不够。
这些问题已通过以下进展得到部分解决:
rclcpp WaitSet:""""""""""""等字样。
等待设置
类允许直接等待订阅、定时器、服务服务器、动作服务器等,而不是使用 Executor。它可用于实现确定性的、用户定义的处理序列,可能同时处理来自不同订阅的多条消息。执行器 examples_rclcpp_wait_set 软件包 提供了几个使用这种用户级等待集机制的示例。rclc 执行人:该执行器来自 C 客户端库 rclc它允许用户对回调的执行顺序进行精细控制,并允许自定义触发条件来激活回调。此外,它还实现了逻辑执行时间(LET)语义的理念。
更多信息
Michael Pöhnl et al: "ROS 2 执行器:如何实现高效、实时和确定性?.2021 年 ROS 世界研讨会。虚拟活动。2021 年 10 月 19 日。
拉尔夫-兰格 "使用 ROS 2 进行高级执行管理".ROS 工业大会。虚拟活动。2020 年 12 月 16 日。
Daniel Casini、Tobias Blass、Ingo Lütkebohle 和 Björn Brandenburg: "基于预约调度的 ROS 2 处理链响应时间分析, Proc. of 31st ECRTS 2019, Stuttgart, Germany, July 2019.