发布时间:2026/8/26 19:45:54
ROS2 执行器(Executor)与回调组(Callback Group) ROS2 作为分布式机器人开发框架其核心事件驱动模型依赖执行器Executor驱动回调函数执行而回调组Callback Group则是控制回调并发策略的核心机制。一、概念1.1 执行器ExecutorROS2 中节点的回调函数订阅、定时器、服务/客户端、动作等不会自动执行必须由 Executor 接管并驱动。Executor 作用监听事件通过rcl_wait监听底层 ROS 中间件的事件如话题消息到达、服务请求就绪事件处理从“等待集wait set”中取出就绪事件调度回调根据回调组的并发规则在 Executor 线程中执行回调函数循环执行持续上述流程直到被显式停止或节点销毁。1.2 回调组Callback Group每个回调函数必须归属一个回调组显式/隐式回调组定义了组内回调的并发执行规则是 Executor 调度的依据。其作用是将不同特性的回调如快速响应/耗时计算、是否共享资源分组避免单回调阻塞整个节点。二、执行器Executor2.1 执行器的类型ROS2 提供 3 种 Executor均继承自rclcpp::Executor基类类型核心特性适用场景SingleThreadedExecutor单线程串行执行所有回调节点默认spin()即使用此类型简单场景、无并发需求、调试方便无线程安全问题MultiThreadedExecutor多线程并行执行回调可配置线程数默认 CPU 核心数回调有阻塞/耗时操作、需高响应性的场景StaticSingleThreadedExecutor静态单线程执行器提前注册所有节点/回调运行时无动态注册开销性能敏感、回调固定的场景如嵌入式设备2.2 Executor APIAPI功能add_node(node)将节点注册到 Executor多节点管理核心remove_node(node)从 Executor 移除节点spin()阻塞式循环持续执行回调直到stop()或节点销毁spin_once(timeout)等待timeout时长执行一个就绪回调无就绪则超时退出spin_some()非阻塞执行所有已就绪的回调无等待stop()终止 Executor 的循环需手动调用否则spin()一直阻塞2.3 特性多节点管理一个 Executor 可注册多个节点统一调度所有节点的回调提升资源利用率线程模型MultiThreadedExecutor的线程数通过set_num_threads(n)配置线程数不宜超过 CPU 核心数避免线程切换开销阻塞特性spin()是阻塞调用若需非阻塞执行需将spin()放在独立线程中。三、回调组Callback Group3.1 回调组的类型回调组的类型由rclcpp::CallbackGroupType定义决定组内回调的并发规则类型核心规则线程安全适用场景MutuallyExclusive默认同一组内回调串行执行不同组可并行Executor 多线程时内置互斥锁无需手动加锁共享资源的回调、快速响应回调Reentrant同一组内回调可并行执行Executor 多线程时无内置锁需用户保证线程安全无共享资源的耗时回调、独立计算任务3.2 回调组的绑定方式隐式绑定创建回调如订阅者时不指定回调组自动加入节点的默认MutuallyExclusive回调组显式绑定手动创建回调组在创建订阅/定时器时通过callback_group参数指定所属组。3.3 特性生命周期回调组与节点绑定节点销毁则回调组自动销毁组间并发MultiThreadedExecutor下不同MutuallyExclusive组的回调可并行执行组内并发仅Reentrant组支持组内并行MutuallyExclusive组内绝对串行。四、Executor 与 Callback Group 的协同规则两者的协同是 ROS2 回调调度的核心规则总结Executor 决定最大并行度线程数回调组决定组内并发规则单线程 Executor 下无论回调组类型如何所有回调均串行执行Reentrant组也无法并行多线程 Executor 下MutuallyExclusive组组内串行组间并行Reentrant组组内/组间均并行需手动保证线程安全默认行为节点spin()等价于SingleThreadedExecutor 默认MutuallyExclusive回调组。五、C 代码示例示例创建不同回调组、绑定订阅/定时器、单/多线程 Executor 对比、Reentrant 组线程安全处理。5.1 完整代码#includerclcpp/rclcpp.hpp#includestd_msgs/msg/string.hpp#includemutex#includechronousingnamespacestd::chrono_literals;classExecutorCallbackGroupDemo:publicrclcpp::Node{public:ExecutorCallbackGroupDemo():Node(executor_cg_demo),counter_(0){// 1. 创建不同类型的回调组 // 互斥型回调组默认cg_mutex_this-create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);// 可重入型回调组cg_reentrant_this-create_callback_group(rclcpp::CallbackGroupType::Reentrant);// 2. 绑定订阅者到不同回调组 // 订阅者1绑定到互斥组处理快速消息sub1_this-create_subscriptionstd_msgs::msg::String(topic_mutex,10,std::bind(ExecutorCallbackGroupDemo::callback_mutex,this,std::placeholders::_1),rclcpp::SubscriptionOptions().callback_group(cg_mutex_)// 显式指定回调组);// 订阅者2绑定到可重入组处理耗时任务sub2_this-create_subscriptionstd_msgs::msg::String(topic_reentrant,10,std::bind(ExecutorCallbackGroupDemo::callback_reentrant,this,std::placeholders::_1),rclcpp::SubscriptionOptions().callback_group(cg_reentrant_));// 3. 绑定定时器到不同回调组 // 定时器1绑定到互斥组1Hz快速任务timer1_this-create_wall_timer(1s,std::bind(ExecutorCallbackGroupDemo::timer_callback_mutex,this),cg_mutex_// 直接指定回调组简化写法);// 定时器2绑定到可重入组2Hz耗时任务timer2_this-create_wall_timer(500ms,std::bind(ExecutorCallbackGroupDemo::timer_callback_reentrant,this),cg_reentrant_);}// ---------- 互斥组回调串行无需手动加锁 ----------voidcallback_mutex(conststd_msgs::msg::String::SharedPtr msg){RCLCPP_INFO(this-get_logger(),[Mutex Group] Sub1 received: %s,msg-data.c_str());std::this_thread::sleep_for(500ms);// 模拟短耗时}voidtimer_callback_mutex(){RCLCPP_INFO(this-get_logger(),[Mutex Group] Timer1 (1Hz) counter: %d,counter_);}// ---------- 可重入组回调并行需手动加锁 ----------voidcallback_reentrant(conststd_msgs::msg::String::SharedPtr msg){std::lock_guardstd::mutexlock(mtx_);// 保护共享资源RCLCPP_INFO(this-get_logger(),[Reentrant Group] Sub2 received: %s (thread: %ld),msg-data.c_str(),std::this_thread::get_id());std::this_thread::sleep_for(1s);// 模拟长耗时}voidtimer_callback_reentrant(){std::lock_guardstd::mutexlock(mtx_);// 保护共享资源RCLCPP_INFO(this-get_logger(),[Reentrant Group] Timer2 (2Hz) counter: %d (thread: %ld),counter_,std::this_thread::get_id());}private:rclcpp::CallbackGroup::SharedPtr cg_mutex_;// 互斥型回调组rclcpp::CallbackGroup::SharedPtr cg_reentrant_;// 可重入型回调组rclcpp::Subscriptionstd_msgs::msg::String::SharedPtr sub1_,sub2_;rclcpp::TimerBase::SharedPtr timer1_,timer2_;intcounter_;// 共享计数器需线程安全std::mutex mtx_;// 保护共享资源的互斥锁};intmain(intargc,char*argv[]){rclcpp::init(argc,argv);autonodestd::make_sharedExecutorCallbackGroupDemo();// 场景1单线程Executor所有回调串行 // rclcpp::executors::SingleThreadedExecutor executor;// executor.add_node(node);// RCLCPP_INFO(node-get_logger(), SingleThreadedExecutor started...);// executor.spin();// 场景2多线程Executor并行执行不同组回调 rclcpp::executors::MultiThreadedExecutor executor;executor.set_num_threads(4);// 设置4个线程executor.add_node(node);RCLCPP_INFO(node-get_logger(),MultiThreadedExecutor (4 threads) started...);executor.spin();rclcpp::shutdown();return0;}5.2 CMakeLists.txt 配置cmake_minimum_required(VERSION 3.8) project(executor_cg_demo) if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES Clang) add_compile_options(-Wall -Wextra -Wpedantic) endif() find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) find_package(std_msgs REQUIRED) add_executable(executor_cg_demo src/executor_cg_demo.cpp) ament_target_dependencies(executor_cg_demo rclcpp std_msgs) install(TARGETS executor_cg_demo DESTINATION lib/${PROJECT_NAME}) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) ament_lint_auto_find_test_dependencies() endif() ament_package()5.3 代码解析与运行效果回调组创建手动创建MutuallyExclusive和Reentrant两组回调组回调绑定订阅者/定时器分别绑定到不同组演示显式绑定方式线程安全处理Reentrant组的回调访问共享变量counter_时通过std::mutex加锁Executor 切换单线程 Executor所有回调串行执行timer22Hz会被sub21s 耗时阻塞实际频率远低于 2Hz多线程 ExecutorMutuallyExclusive组内串行Reentrant组内并行timer2和sub2可同时执行控制台会打印不同线程 ID。六、常见坑点与实践6.1 常见坑点单线程 Executor 阻塞若回调中有sleep()/IO 阻塞所有其他回调会被挂起解决方案改用多线程 Executor 分组Reentrant 组线程安全未加锁访问共享资源会导致数据错乱如counter_计数错误忘记注册节点手动创建 Executor 时未调用add_node()会导致回调不执行线程数过度配置MultiThreadedExecutor线程数超过 CPU 核心数会增加线程切换开销重复 spin() 调用同一线程多次调用spin()会导致永久阻塞需在独立线程中调用。6.2 实践分组策略快速响应回调如传感器数据接收放入MutuallyExclusive组避免被耗时回调阻塞耗时回调如路径规划、图像处理放入独立MutuallyExclusive组或Reentrant组无共享资源时共享资源的回调放入同一MutuallyExclusive组利用内置锁避免手动加锁Executor 选择简单场景使用默认spin()SingleThreadedExecutor复杂场景使用MultiThreadedExecutor线程数设为 CPU 核心数如 4/8性能敏感场景使用StaticSingleThreadedExecutor非阻塞 spin若需节点同时处理其他逻辑将spin()放入独立线程std::threadspin_thread([executor](){executor.spin();});// 主线程处理其他逻辑spin_thread.join();七、相关补充spin_once()/spin_some() 用法spin_once(100ms)等待 100ms若有就绪回调则执行一个否则退出spin_some()立即执行所有就绪回调无阻塞适合循环中轮询跨节点 Executor一个 Executor 可注册多个节点示例autonode1std::make_sharedNode1();autonode2std::make_sharedNode2();executor.add_node(node1);executor.add_node(node2);executor.spin();Executor 停止调用executor.stop()可终止spin()循环需在其他线程调用默认回调组节点的get_default_callback_group()可获取默认组用于批量绑定回调动态调整线程数MultiThreadedExecutor运行时可通过set_num_threads()动态调整线程数。ROS2 的 Executor 与 Callback Group 是控制回调执行的核心掌握两者的协同规则是编写高性能 ROS2 节点的关键Executor 决定“用多少线程执行回调”Callback Group 决定“哪些回调可以并行执行”单线程 Executor 保证简单性多线程 Executor 提升响应性回调组则是平衡并发与线程安全的核心手段。

相关新闻

2026/8/26 19:45:54

E2E_端到端的ViT神经网络迁移机械臂动作工艺

机械臂的仿射学习,在无相机标定条件(即不知道内外参)、但运算资源充足的前提下,可以放弃依赖精确3D重投影的方案,转而采用纯视觉、自监督、集合感知的端到端学习框架。目标仍是:从多视角视频直接预测新机械…

2026/8/26 19:45:54

k8s的pod管理与控制器

一、pod:1.Pod 核心概念Pod 是 K8s 最小调度单元,不是容器;一个 Pod 可以运行 1 个或多个容器。 Pod 内容器共享:网络命名空间 (Pod‑IP)、共享存储 (volume)。 Pod 生命周期短暂:Pod 删除后不能复活,重建就…

2026/8/26 20:45:58

V4L2视频采集实战:从零掌握Linux摄像头数据流与缓冲区模型

1. 项目缘起:为什么需要深入理解V4L2?如果你在Linux下玩过摄像头,尤其是想自己写程序从USB摄像头或者像树莓派CSI接口这类嵌入式摄像头里拿数据,那你大概率听说过V4L2这个名字。我第一次接触它,是想在一个嵌入式Linux板…

2026/8/26 20:45:58

靠谱的儿童陪伴服务公司名声

很多家长问:“哪家儿童陪伴服务公司名声好?”但名声是结果,不是原因。真正靠谱的公司,靠的是可验证的筛选、培训、匹配、督导体系。启萌心(四川)教育科技有限公司深耕0-12岁儿童陪伴教育,用一套…

2026/8/26 20:45:58

从调研到定稿:5类文献综述AI工具实测与全流程搭配攻略

先说结论:没有万能的AI,只有最适配你场景的工具。 去年帮学妹改文献综述,前前后后试了不下十款工具——从ChatGPT、Claude到国产大模型,从垂直学术平台到文献检索神器,踩坑无数。有的生成速度飞快,结果参考…

2026/8/26 20:45:58

提升后端性能,先学会优化数据库查询

凌晨三点,监控大屏上那根刺眼的红线还在往上爬。后端服务的CPU飙到90%,数据库连接池被占满,响应时间从200ms一路狂飙到3秒。你打开慢查询日志,发现罪魁祸首是一条跑了4.2秒的SQL——它不过是想查一张三千万行的订单表里某个用户的…

2026/8/26 20:45:58

彻底卸载奇安信天擎:从密码破解到强制清除的完整指南

1. 从一次“不请自来”的安装说起 那天下午,我正在调试一个本地服务,突然发现网络连接变得异常卡顿,CPU占用率也莫名飙升。打开任务管理器一看,一个名为“360天擎”的进程赫然在列,正以“管理员”身份运行,…

2026/8/26 20:40:57

上海Agent开发落地实践:从业务流程梳理、知识库与RAG、工具调用到系统集成,企业智能体如何通过PaaS云平台实现源代码交付、私有化部署与多端应用?以D-coding为例说明选型

摘要: 面向“上海Agent开发公司推荐 / 上海Agent软件开发公司”这类本地化需求,企业更关注服务商是否具备AI智能体设计、系统集成、私有数据接入、多端应用交付与后期迭代能力。**D-coding(研发主体:上海担路网络科技有限公司&…

2026/8/26 9:13:28

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/25 11:48:27

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/25 16:56:43

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/26 0:04:32

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 1:19:35

JSON总结

JSON概念 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,主要用于跟服务器进行交换数据。它基于ECMAScript的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C、C、C#、Java、JavaScr…

2026/8/26 1:19:35

保存连接sse 是什么原理,为什么不会一直请求

“保持连接”用的是 SSE(Server-Sent Events),本质是一个没有马上结束的 HTTP 请求。 过程是: 拷贝机发送一次请求: GET /api/code-sync/events服务器返回: Content-Type: text/event-stream但不关闭响应&…

2026/8/26 19:34:06

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/26 19:17:08

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/26 19:34:05

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…