news 2026/10/5 7:31:05

C++ 第 37 课:Executor 是什么?它和 spin() 到底什么关系?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++ 第 37 课:Executor 是什么?它和 spin() 到底什么关系?

上一课标准答案:

  1. 摄像头连续发送图片:Topic。因为是持续的数据流。
  2. 查询一次机械臂是否 Ready:Service。因为是一次请求、一次回答。
  3. 让机械臂执行 5 秒轨迹并查看进度:Action。因为任务持续一段时间,还需要 Feedback / Result / Cancel。
  4. Action 三个核心:
Goal 目标 Feedback 执行过程反馈 Result 最终结果
  1. spin()和 Action 当然有关系。Action 的 Goal、Cancel、反馈处理等最终都属于 ROS2 事件,需要 Executor 调度对应 callback。

第 37 课:Executor 是什么?它和spin()到底什么关系?

这一课非常关键。

前面我们一直把:

rclcpp::spin(node);

叫“调度员”。

严格来说,现在要修正:

真正负责调度 callback 的核心东西叫 Executor。spin()更像是让 Executor 开始持续工作。


1. 先区分旧知识和新知识

【旧知识】

你已经学过:

Node Subscriber Timer Service Action callback spin

【新知识】

现在加入:

Executor

Executor 中文可以先理解成:

执行器 / callback 调度器


2. 为什么之前一直说 spin 是“调度员”?

因为初学阶段这样最容易理解:

spin ↓ 等事件 ↓ 调用callback

但实际上更准确的是:

Node ↓ 交给 Executor ↓ Executor 等待事件 ↓ Executor 选择可执行 callback ↓ Executor 执行 callback

而:

rclcpp::spin(node);

只是 ROS2 给你的一个方便写法。


3. 可以先把spin(node)想成“快捷模式”

例如:

rclcpp::spin(node);

你可以先粗略理解成 ROS2 帮你做了类似:

创建一个Executor ↓ 把node加入Executor ↓ 让Executor开始spin

所以:

spin()是“启动事件循环”,Executor 才是里面真正负责调度 callback 的角色。


4. 用公司类比

假设:

Node = 一个部门

这个部门里有:

Subscriber Timer Service Action

它们都会产生任务。

例如:

Subscriber: “有新消息来了” Timer: “到时间了” Service: “有请求来了” Action: “有Goal来了”

谁决定先处理哪个?

Executor。

所以:

Node │ ├── Subscriber事件 ├── Timer事件 ├── Service事件 └── Action事件 ↓ Executor ↓ 选择callback ↓ 执行

5. Executor 处理的不是“Publisher 消息本身”

这个地方一定要继续区分。

Executor 主要看的是:

哪些 callback 已经具备执行条件了。

例如:

Subscriber收到消息 ↓ subscriber callback ready

或者:

Timer到时间 ↓ timer callback ready

Executor 再去:

挑一个 ready callback ↓ 执行

6. Publisher 在里面是什么角色?

Publisher 通常不是一个“需要 callback 才能自己运作的事件源”。

你在 callback 里可以直接:

publisher_->publish(msg);

于是:

callback ↓ Publisher发布消息 ↓ Topic ↓ 另一个Subscriber收到 ↓ 另一个callback变成ready ↓ 另一个Executor调度

所以 Publisher 更像:

主动发送工具

而 Subscriber/Timer/Service/Action 常常会产生“待执行 callback”。


7. 一个完整例子

假设一个节点有:

Subscriber:/vla_action Timer:每20ms

运行:

rclcpp::spin(node);

实际逻辑可以粗略理解成:

Executor开始工作 ↓ 等待 10ms: /vla_action消息来了 ↓ actionCallback ready ↓ Executor执行actionCallback 20ms: Timer到期 ↓ controlCallback ready ↓ Executor执行controlCallback 40ms: Timer再次到期 ↓ Executor再次执行controlCallback

8. 最重要的新问题:如果两个 callback 同时 ready 呢?

例如:

/vla_action消息刚好来了 + Timer也刚好到期

那么现在有两个 callback:

actionCallback controlCallback

都 ready。

这时候:

Executor 决定怎么调度。

这正是下一层 ROS2 运行机制的核心。


9. 最简单的 Executor:单线程 Executor

你以后会看到:

SingleThreadedExecutor

可以理解成:

只有一个工作人员。

假设两个 callback 同时 ready:

actionCallback controlCallback

只有一个线程,所以:

先执行一个 ↓ 执行完 ↓ 再执行另一个

不能真正同时跑。


10. 为什么这对 VLA 特别重要?

假设:

void vlaCallback(...) { // VLA推理 // 耗时2秒 }

而你还有:

Timer: 每20ms执行一次控制

如果使用单线程 Executor:

VLA callback 开始 ↓ 推理2秒 ↓ 这2秒线程被占用 ↓ Timer callback不能及时执行

于是机器人控制可能:

被 VLA 推理“卡住”。

这个问题非常真实。


11. 形象一点

单线程 Executor 像只有一个服务员。

现在:

顾客A: “帮我跑一次VLA推理” 需要2分钟 顾客B: “每20ms给我更新控制”

服务员正在处理 A:

A没做完 ↓ B只能排队

所以 B 延迟。


12. 这就是为什么 ROS2 还有多线程 Executor

以后你会看到:

MultiThreadedExecutor

可以理解成:

有多个工作人员。

这样理论上:

线程1: 执行VLA callback 线程2: 执行Timer callback

就可能同时处理。

当然真实情况还涉及:

Callback Group 线程安全 mutex 共享数据

这些以后再学。


13. 所以spin()和 Executor 最准确关系

你现在可以把之前的理解升级为:

Node ↓ 交给Executor管理 ↓ Executor持续等待ROS2事件 ↓ 哪些callback ready? ↓ 选择并执行callback

而:

rclcpp::spin(node);

可以理解成:

ROS2 提供的便捷接口,让一个 Executor 管理这个 node 并开始持续执行。


14. 一个 Node 一定只有一个 callback 吗?

当然不是。

一个 Node 可以:

VLAController Node │ ├── actionCallback ├── jointStateCallback ├── timerCallback ├── serviceCallback └── actionServerCallback

Executor 负责调度这些 callback。


15. 一个 Executor 只能管理一个 Node 吗?

也不是。

Executor 可以管理多个 Node。

例如:

Executor │ ├── CameraNode ├── VLAController └── RobotController

这些 Node 的 callback 都可以由同一个 Executor 调度。

这也是你之前说“spin 是各个节点对象里面的调度员”需要稍微修正的地方。

更准确:

Executor 不一定属于某一个 Node 内部;它可以在外部统一管理一个或多个 Node。


16. 这张图最重要

Executor │ ┌───────┼────────┐ │ │ │ Node A Node B Node C │ │ │ callbacks callbacks callbacks │ │ │ └───────┴────────┘ ↓ Executor调度执行

17. 放进你的 VLA 机器人系统

以后可能:

CameraNode ↓ 图像Topic VLANode ↓ VLA推理 RobotController ↓ 机械臂控制

如果所有 callback 都放进一个:

SingleThreadedExecutor

而 VLA 推理耗时很长:

控制和传感器 callback 都可能被阻塞。

所以真实工程里可能需要:

MultiThreadedExecutor + Callback Groups + 把重计算放到独立线程/进程

这个就是你以后做 VLA + ROS2 时必须理解的工程问题。


本课核心压缩

记住 4 句话:

1. Node 里面有各种callback 2. Subscriber/Timer/Service/Action会让callback变成ready 3. Executor负责选择并执行ready callback 4. spin让Executor持续工作

以及:

SingleThreadedExecutor → 一个callback执行时,其他callback可能排队 MultiThreadedExecutor → 多个callback可能并发执行
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 7:30:50

AXI转PCIe工程实战:从IP配置到上板调试与性能优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 7:30:36

SpringBoot迁移宝兰德BES 9.5.5信创改造避坑实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 7:28:31

VSCode C/C++调试配置化解:三文件协作与断点命中指南

VSCode调试C/C,说简单也简单,说麻烦是真麻烦。我见过太多人装了C/C插件就直接按F5,界面弹出一堆launch.json配置错误,或者编译通了却永远打不上断点,最后怀疑人生地回到Visual Studio的怀抱。其实C/C调试在VSCode里的核…

作者头像 李华
网站建设 2026/10/5 7:28:24

RestTemplate深度实战:从基础调用到文件上传与性能调优

restTemplate这个类,基本是Spring项目里绕不开的HTTP客户端工具。我接手过不少老项目,Controller里调第三方接口的代码十有八九是new RestTemplate然后getForObject,看起来一切正常,真跑到线上就开始出幺蛾子。这篇东西我想一次性…

作者头像 李华
网站建设 2026/10/5 7:27:47

FPGA控制DDR读写(AXI4总线接口)实战:从架构到调试全流程

从第一块FPGA板卡到现在,我接过不少和FPGA控制DDR读写相关的项目,最常见的是图像采集卡的帧缓存、高速ADC采样数据的暂存、还有软件无线电里的波形回放。DDR本身是一个相对成熟的内存颗粒,但真正要在FPGA工程里把读写跑稳、跑快,总…

作者头像 李华