上一课答案
1. 真正负责调度 callback 的是谁?
是:
Executor
spin() 更像是:
让 Executor 持续开始工作。
2. rclcpp::spin(node) 和 Executor 的关系
可以粗略理解:
rclcpp::spin(node)
↓
ROS2帮你建立/使用Executor
↓
把node交给它
↓
Executor持续等待事件
↓
执行ready的callback
3. Publisher 是不是主要靠 Executor 调度发送?
不是。
例如:
publisher_->publish(msg);
这句被执行时,就主动调用 Publisher 发布消息。
Publisher 本身不像 Subscriber / Timer 那样主要依赖一个"callback ready 后等待 Executor 执行"的模式。
但:
publish()经常是在某个 callback 内部被调用。
4. 单线程 Executor 中,VLA callback 耗时 2 秒怎么办?
假设:
VLA callback
耗时2秒
同时 Timer:
每10ms ready一次
如果是单线程 Executor:
VLA callback执行
↓
唯一线程被占用2秒
↓
Timer callback无法及时执行
↓
只能等待
所以控制周期会严重延迟。
5. Executor 能不能管理多个 Node?
可以。
例如:
Executor
├── CameraNode
├── VLANode
└── RobotControllerNode
Executor 可以统一调度多个节点中的 callback。
第 38 课:线程、SingleThreadedExecutor、MultiThreadedExecutor、Callback Group
这一课同时补 C++ 并发基础 + ROS2 并发模型。
1. 先学一个新的 C++ 概念:线程 Thread
这是【C++ 新知识】。
你以前可以把程序想成:
第1句
↓
第2句
↓
第3句
这相当于:
一条执行路线。
这条执行路线就可以粗略理解成一个:
Thread
线程
例如:
void taskA()
{
// 做A
}
void taskB()
{
// 做B
}
如果只有一个线程:
taskA
↓
执行完
↓
taskB
不能真正同时执行。
2. 多线程是什么?
假设两个线程:
线程1 → taskA
线程2 → taskB
那么在多核 CPU 上,它们可能真正并行运行。
例如:
线程1:
VLA推理
线程2:
机械臂控制
这正是机器人系统为什么需要并发。
3. SingleThreadedExecutor
ROS2:
SingleThreadedExecutor
意味着:
Executor 使用一个主要执行线程处理 callback。
假设:
Callback A:VLA推理,2000ms
Callback B:控制循环,10ms
执行:
A开始
████████████████ 2秒
A结束
B才能开始
于是 B 被卡住。
4. MultiThreadedExecutor
如果:
MultiThreadedExecutor
有多个工作线程。
可能变成:
线程1:
████████ VLA callback ████████
线程2:
control
control
control
control
...
这样:
VLA 推理不会必然完全阻塞控制 callback。
5. 但"多线程"立刻带来一个新问题
假设两个 callback 都访问:
double target_angle;
Callback A:
target_angle = 30.0;
Callback B:
double command = target_angle;
如果同时发生:
线程1正在写 target_angle
线程2正在读 target_angle
就可能发生:
数据竞争 Data Race
这是 C++ 并发里极其重要的概念。
6. 什么叫数据竞争?
假设:
int x = 0;
线程 A:
x = 100;
线程 B:
std::cout << x;
如果两个线程没有任何同步机制,同时操作:
同一个变量
+
至少一个线程写
就可能出现数据竞争。
结果可能不可预测。
7. ROS2 里这为什么非常现实?
未来你可能有:
actionCallback
↓
修改 target_joint_
controlCallback
↓
读取 target_joint_
如果 MultiThreadedExecutor 同时执行:
线程1
actionCallback()
修改目标
线程2
controlCallback()
读取目标
那么共享数据:
target_joint_
就必须认真处理线程安全。
8. 所以不能简单认为"多线程更快更好"
单线程:
优点:
简单
没有大量并发问题
缺点:
一个callback太慢会阻塞其他callback
多线程:
优点:
不同callback可能并发
缺点:
共享数据
竞态
锁
线程安全
调试难度
所以工程设计不是:
"全部改 MultiThreadedExecutor 就完事。"
9. ROS2 引入 Callback Group
这是【ROS2 新知识】。
Callback Group 可以理解成:
给 callback 分组,并告诉 Executor 哪些 callback 能不能同时执行。
最重要的两类:
MutuallyExclusive
Reentrant
10. MutuallyExclusive
意思:
同一个 group 里的 callback 不允许同时执行。
比如:
Callback Group A
├── jointStateCallback
└── controlCallback
设置为:
MutuallyExclusive
那么即使使用多线程 Executor:
jointStateCallback
和
controlCallback
也不会同时执行。
11. 为什么有用?
因为它们可能共同访问:
joint_state_
你不希望一个正在修改,一个正在读取。
所以:
互斥执行
可以降低并发风险。
12. Reentrant
意思:
这个 group 里的 callback 可以并发执行,甚至同一种 callback 也可能同时被调用。
适合:
本身就是线程安全、互不影响的任务。
这个比 MutuallyExclusive 更需要你理解线程安全。
13. 所以 Executor 和 Callback Group 是不同层级
可以画:
MultiThreadedExecutor
│
▼
Node
│
┌────┴────┐
│ │
Group A Group B
│ │
callback callback
Executor:
负责调度。
Callback Group:
限制哪些 callback 可以并发。
14. VLA 场景怎么设计?
例如:
RobotController Node
│
├── Group A:MutuallyExclusive
│ ├── jointStateCallback
│ └── controlTimerCallback
│
└── Group B
└── vlaCallback
概念目标:
控制和状态更新
→ 尽量保证一致性
VLA推理
→ 不要长时间堵住控制循环
但真实系统里,VLA 大模型通常还会进一步:
独立进程 / 独立 Node / Python 节点 / 推理服务器
而不是简单塞进一个 callback 里跑几秒。
15. 为什么更推荐把 VLA 和控制拆成不同 Node?
例如:
VLANode
Python + PyTorch
负责:
视觉
语言
模型推理
动作生成
和:
RobotController
C++ + ROS2
负责:
高频状态
安全检查
轨迹下发
控制
中间:
Topic / Action
通信。
这就是 VLA 系统非常合理的一种架构:
Python VLA
↓
低频高级动作
↓
ROS2通信
↓
C++控制节点
↓
高频可靠控制
而不是:
C++控制callback里面
直接跑20亿参数模型
16. C++真正的多线程以后怎么写?
你以后会学:
#include <thread>
std::thread t(task);
以及:
std::mutex
std::lock_guard
std::condition_variable
std::atomic
这些不会只作为 ROS2 附属概念。
我们会专门系统学习:
线程
共享内存
锁
死锁
数据竞争
原子变量
条件变量
线程生命周期
因为这本身就是现代 C++ 的重要部分。
17. 当前理解层级
现在先形成:
程序
↓
可以有多个线程
Executor
↓
用线程执行ROS2 callback
SingleThreadedExecutor
↓
基本一个执行线程
MultiThreadedExecutor
↓
允许多个callback并发
Callback Group
↓
进一步约束哪些callback允许并发
共享数据
↓
必须考虑线程安全
这条链非常重要。
18. VLA 实际案例
假设:
VLA输出周期:
500ms
机械臂控制周期:
10ms
如果两者强行单线程:
VLA执行500ms
↓
控制循环被卡500ms
对机械臂几乎不可接受。
合理架构更接近:
VLA Node
500ms一次高级动作
│
▼
ROS2 Topic
│
▼
RobotController
10ms控制循环
两边不同运行节奏。
所以:
ROS2 不只是通信工具,它还帮助你把不同时间尺度的机器人功能拆开。
本课小练习
-
SingleThreadedExecutor 为什么容易被长时间 callback 阻塞?
-
MultiThreadedExecutor 最大的优势和最大的风险分别是什么?
-
两个线程同时操作同一个变量,并且至少一个线程写,可能产生什么问题?
-
MutuallyExclusiveCallback Group 主要限制什么? -
如果 VLA 推理 500ms,而机械臂控制需要 10ms 一次,你更倾向把它们放在同一个阻塞 callback,还是拆开?为什么?