当前位置: 首页 > news >正文

ROS 2系统Callback Group概念笔记

核心概念

Callback Group(回调组)是一个管理一个或多个回调函数执行规则的容器。它决定了这些回调函数是如何被节点(Node)的 executor 调度的,特别是当多个回调函数同时就绪时,它们之间是并行执行还是必须串行执行。

理解回调组的关键在于理解它与 Executor 的交互。Executor 是 ROS 2 中负责检查订阅、定时器、服务、动作服务器等是否就绪,并调用其对应回调函数的组件。


为什么需要 Callback Group?

例如一个节点,它有以下组件:

  • 一个激光雷达(Lidar)订阅者:回调函数 lidar_callback,处理高频、数据量大的点云数据。
  • 一个键盘输入订阅者:回调函数 keyboard_callback,处理用户偶尔的按键输入。
  • 一个服务服务器:回调函数 save_data_service_callback,处理保存数据的请求。

如果没有回调组,所有回调函数默认都在同一个组里。当 Executor 发现多个回调函数同时就绪时(例如,正在处理激光数据时用户按下了键),它会将它们全部放入一个队列,然后逐个执行。

这可能会导致一个问题:处理庞大的激光雷达数据可能会阻塞 keyboard_callbacksave_data_service_callback 很长时间,导致用户输入或服务请求响应非常迟钝,体验很差。

回调组就是为了解决这种回调函数之间的相互干扰问题而设计的。


回调组的类型

ROS 2 主要提供了两种类型的回调组:

1. Mutually Exclusive (互斥型) - 默认类型
  • 行为:属于同一个互斥组的所有回调函数不能同时执行。Executor 会像处理一个队列一样,一次只执行其中一个。
  • 类比:单线程。任务一个接一个地做。
  • 适用场景:这是最常用也是默认的类型。适用于回调函数之间没有严格的实时性要求,或者它们会访问共享资源需要互斥锁保护的情况。
2. Reentrant (可重入型)
  • 行为:属于同一个可重入组的所有回调函数可以同时被执行。Executor 会尽可能同时调用它们(如果系统有多个CPU核心,则真正并行)。
  • 警告:使用此类型需要非常小心。你必须确保回调函数是线程安全的。如果它们会访问共享的变量或资源,你必须自己使用锁(如 std::mutex)来保护,否则会导致数据竞争和未定义行为。
  • 类比:多线程。多个任务可以同时进行。
  • 适用场景:适用于那些相互独立、没有共享数据、且对实时性要求很高的回调函数。例如,一个控制电机的高频定时器回调和一个发布状态的低频定时器回调。

使用回调组

你需要在创建订阅者、定时器、服务等之前先创建回调组,然后在创建这些对象时将回调组作为参数传入。

以下是 C++ 和 Python 的示例代码:

C++ 示例
#include “rclcpp/rclcpp.hpp”class MyNode : public rclcpp::Node {
public:MyNode() : Node(”my_node”) {// 1. 创建回调组// 互斥型 (默认就是这个,显式写出更清晰)mutually_exclusive_group_ = this- >create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);// 可重入型reentrant_group_ = this- >create_callback_group(rclcpp::CallbackGroupType::Reentrant);// 2. 设置选项,在创建对象时指定所属的回调组rclcpp::SubscriptionOptions options;options.callback_group = mutually_exclusive_group_;// 创建订阅者,并指定它属于 mutually_exclusive_group_lidar_sub_ = this- >create_subscription< sensor_msgs::msg::LaserScan >(/scan”, 10,std::bind(&MyNode::lidar_callback, this, std::placeholders::_1),options); // 传入 options// 对于不需要特殊选项的对象,可以直接传递 callback_group 参数// 这个定时器属于 reentrant_group_,可以和上面的订阅者并行执行timer_ = this- >create_wall_timer(std::chrono::seconds(1),std::bind(&MyNode::timer_callback, this),reentrant_group_); // 直接传入回调组}private:void lidar_callback(const sensor_msgs::msg::LaserScan::SharedPtr msg) {// 处理激光数据,可能很耗时}void timer_callback() {// 定时执行的任务}rclcpp::CallbackGroup::SharedPtr mutually_exclusive_group_;rclcpp::CallbackGroup::SharedPtr reentrant_group_;rclcpp::Subscription< sensor_msgs::msg::LaserScan >::SharedPtr lidar_sub_;rclcpp::TimerBase::SharedPtr timer_;
};int main(int argc, char * argv[]) {rclcpp::init(argc, argv);auto node = std::make_shared< MyNode >();rclcpp::spin(node);rclcpp::shutdown();return 0;
}

回调组与执行器(Executor)的配合

  • SingleThreadedExecutor:即使是可重入组,回调函数也无法真正并行,因为只有一个线程。但 Executor 会通过技巧(如在等待服务响应时切换到其他可执行回调)来模拟并发,提高响应效率。
  • MultiThreadedExecutor:这是发挥可重入组威力的地方。该执行器内部有一个线程池。当一个可重入组有多个回调就绪时,执行器可以从线程池中取出多个线程来真正并行地执行它们。

最佳实践:如果你使用了可重入回调组,通常应该配合 MultiThreadedExecutor 来使用,以实现真正的并行处理。

总结

特性Mutually Exclusive (互斥)Reentrant (可重入)
执行方式串行并行
线程安全不需要额外考虑(默认安全)必须自行保证线程安全
性能可能因长回调导致阻塞响应性更好,资源利用率更高
适用场景默认选择,共享资源需保护实时性要求高,回调间完全独立

核心:Callback Group 是一种强大的工具,让你能够精细地控制节点内部回调函数的执行流程,从而优化节点的响应性和性能,避免不必要的阻塞。在设计复杂的节点时,合理地使用回调组是非常重要的一环。

http://www.dtcms.com/a/340881.html

相关文章:

  • 突发!DeepSeek刚刚开源V3.1-Base
  • UTF-8 编解码可视化分析
  • 【Day 30】Linux-SQL语句
  • C/C++ 与嵌入式岗位常见笔试题详解
  • MYSQL为什么会发生死锁,怎么解决
  • 第三阶段数据-3:数据库脚本生成,备份与还原,分离与附加
  • configtx通道配置文件
  • RHCA08内存管理
  • 对称加密算法
  • 数据库DML语言(增、删、改)
  • 闪电赋能全链路:领码SPARK一体化创新平台
  • 基于HTTP3的WebTransport实践
  • 基于 Java 和 MySQL 的精品课程网站
  • 在完全没有无线网络(Wi-Fi)和移动网络(蜂窝数据)的环境下,使用安卓平板,通过USB数据线(而不是Wi-Fi)来控制电脑(版本2)
  • Ubuntu 重连usb设备(断电和不断电方案)亲测可行
  • 亚马逊新品爆单策略:从传统困境到智能突破
  • LeetCode热题100--101. 对称二叉树--简单
  • C++ 力扣 438.找到字符串中所有字母异位词 题解 优选算法 滑动窗口 每日一题
  • 《数据之舞》
  • GitHub宕机生存指南:从应急协作到高可用架构设计
  • QT-图像灰度处理时QImage.setPixel方法存在的坑
  • 在QT中动态生成控件造成界面卡顿时的鼠标处理
  • Qt设置软件使用期限【新版防修改系统时间】
  • 一个 WPF 文档和工具窗口布局容器
  • GitHub宕机应急指南:无缝协作方案
  • Eclipse 里Mybatis的xml的头部报错
  • 软考高级--系统架构设计师--案例分析真题解析
  • Java项目基本流程(五)
  • DeepSeek API 申请与 Node.js 对接指南
  • 服务器硬件电路设计之 SPI 问答(一):解密 SPI—— 从定义到核心特性