上一课标准答案:
-
MyNode是 C++ 类,不是运行时对象。真正的对象要等你写:auto node = std::make_shared
();
之后才创建出来。因为 MyNode 继承了 rclcpp::Node,这个对象同时具备 ROS2 Node 的能力。
auto node = std::make_shared<MyNode>();
这里的 node 是一个:
std::shared_ptr<MyNode>
也就是管理 MyNode 对象的智能指针。可以画成:
node
│
▼
MyNode对象
├── Node父类部分
└── MyNode自己的部分
rclcpp::init(argc, argv);
是【ROS2 新知识】,作用是:
初始化 ROS2 C++ 运行环境,让后面的 Node、Publisher、Subscriber 等 ROS2 功能可以工作。
rclcpp::spin(node);
不能理解成"什么也不干地卡住"。更准确地说:
让 ROS2 持续处理这个 Node 的事件。
包括消息、Timer、Service、Action 等事件。
- 如果没有
spin(),程序创建完节点后通常会一路执行到结束,Subscriber 没有持续处理事件的机会,所以即使消息到了,callback 也可能根本不会被执行。
第 33 课:事件循环------spin() 到底在干什么
这节非常关键,因为它决定你是否真正理解 ROS2 的运行方式。
先把两个概念分开:
【旧知识】
普通C++代码通常顺序执行
【ROS2新知识】
ROS2大量采用"事件驱动"
1. 普通 C++ 是什么感觉?
例如:
int main()
{
std::cout << 1 << std::endl;
std::cout << 2 << std::endl;
std::cout << 3 << std::endl;
return 0;
}
执行顺序:
1
↓
2
↓
3
↓
程序结束
非常明确:
我写哪一句,CPU 就按顺序执行哪一句。
2. 但机器人程序不能只按一次顺序跑完
假设一个机械臂控制节点需要等待:
VLA动作
关节状态
相机数据
Timer
急停消息
问题是:
这些东西什么时候来?
不知道。
例如:
0.2秒后可能有JointState
1秒后可能有相机图像
1.3秒后VLA才输出动作
所以程序不能这样:
getVLAAction();
getJointState();
getCameraImage();
因为你不知道这些数据什么时候准备好。
ROS2 更适合:
我先注册好"事件来了该怎么办",然后持续等待。
3. 什么叫"事件"?
事件可以非常简单地理解成:
某件值得程序处理的事情发生了。
例如:
新Topic消息到达
Timer到时间
Service请求到达
Action请求到达
这些都叫事件。
4. callback 是"事件发生后的处理办法"
例如我们提前写:
void callback(const Msg::SharedPtr msg)
{
std::cout << msg->data;
}
意思只是:
如果消息来了,我想这么处理。
注意:
定义 callback ≠ callback 正在执行。
这和你定义普通函数一样:
void test()
{
}
只有被调用时它才执行。
5. 那到底谁调用 callback?
这正是:
rclcpp::spin(node);
背后的核心机制之一。
可以粗略理解为:
spin开始
↓
检查有没有事件
↓
没有?
↓
继续等待
↓
有事件?
↓
找到对应callback
↓
执行callback
↓
执行完
↓
继续等待下一个事件
这就是:
事件循环 Event Loop
6. 你可以把 spin() 想成前台值班员
假设一个 Node 是机器人控制中心。
它已经登记了:
收到JointState → 调 jointCallback
收到VLA Action → 调 actionCallback
Timer到时间 → 调 timerCallback
然后:
rclcpp::spin(node);
相当于:
"从现在开始值班,有事情发生就按登记表处理。"
于是:
spin
│
├─ /joint_states来了
│ ↓
│ jointCallback()
│
├─ /vla_action来了
│ ↓
│ actionCallback()
│
├─ Timer到时间
│ ↓
│ timerCallback()
│
└─ 继续值班......
7. 为什么 callback 看起来是"自动执行"?
严格来说,不是 callback 有魔法。
真正发生的是:
你提前把callback注册给ROS2
↓
spin持续等待事件
↓
事件发生
↓
ROS2执行器发现事件
↓
调用你之前注册的callback
所以:
callback 并不是"自己执行",而是 ROS2 在事件发生后替你调用。
这个理解非常重要。
8. 把 Subscriber 放进来
假设:
Subscriber监听:
/number
注册 callback:
void numberCallback(
const std_msgs::msg::Int32::SharedPtr msg)
{
std::cout << msg->data;
}
然后:
rclcpp::spin(node);
程序进入持续事件处理状态。
此时 Publisher 发:
data = 100
整个过程:
Publisher
↓
/number
↓
新消息进入ROS2
↓
spin正在处理事件
↓
发现Subscriber有新消息
↓
调用numberCallback(msg)
↓
msg->data
↓
100
↓
callback结束
↓
spin继续等待
这就是 ROS2 Topic 通信真正"跑起来"的方式。
9. spin() 为什么通常一直不返回?
看:
rclcpp::spin(node);
rclcpp::shutdown();
一般运行 ROS2 节点时,你会发现程序长期停在:
spin(node);
不是程序坏了。
而是:
它正在持续值班。
正常情况下可能一直:
等待
处理
等待
处理
......
直到你终止节点,例如:
Ctrl + C
然后 spin() 才退出,再往下:
rclcpp::shutdown();
10. spin() 和 while(true) 有点像,但不要完全等同
你以前学过:
while (true)
{
}
它会一直循环。
所以从直觉上:
rclcpp::spin(node);
确实有一点像:
while (ROS2还在运行)
{
等待事件;
处理事件;
}
但 ROS2 的真实内部机制比这个复杂。
现阶段这样理解足够:
spin()= ROS2 的持续事件处理循环。
11. Node、Subscriber、callback、spin 各负责什么?
这四个必须分清:
| 概念 | 作用 |
|---|---|
| Node | ROS2 中工作的程序单元 |
| Subscriber | 监听某个 Topic |
| callback | 消息来了以后具体怎么处理 |
| spin | 持续等待并调度这些事件 |
所以不是:
Subscriber一直自己跑
而是更接近:
Node里面有Subscriber
↓
spin让Node持续处理事件
↓
Subscriber消息到达
↓
callback执行
12. VLA 场景下一下就很清楚了
以后你的 ROS2 控制节点可能:
VLAController Node
│
├── Subscriber:/vla_action
│ ↓
│ actionCallback()
│
├── Subscriber:/joint_states
│ ↓
│ jointStateCallback()
│
└── Publisher:/joint_command
程序:
rclcpp::spin(node);
以后:
JointState 来了
/joint_states
↓
jointStateCallback()
↓
更新当前机械臂状态
VLA Action 来了
/vla_action
↓
actionCallback()
↓
读取目标动作
↓
发布机器人控制命令
然后:
继续spin
所以这是一个长期在线的机器人程序。
13. 为什么机器人软件特别适合这种结构?
因为机器人世界本来就是异步的:
相机不会等VLA
VLA不会精确每10ms输出一次
机械臂反馈不断到来
传感器各有自己的频率
如果所有东西都强行顺序执行:
等相机
↓
等VLA
↓
等机械臂
↓
再等相机
程序会非常笨重。
ROS2 用事件机制,可以让:
谁的数据来了
↓
就处理谁
这也是你后面理解整个机器人软件架构的基础。
14. 再看完整 main()
int main(int argc, char** argv)
{
rclcpp::init(argc, argv);
auto node =
std::make_shared<MyNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
现在每一行都可以翻译:
main()
程序入口
↓
rclcpp::init(...)
初始化ROS2环境
↓
make_shared<MyNode>()
创建MyNode节点对象
↓
spin(node)
让节点持续等待并处理ROS2事件
↓
shutdown()
退出ROS2
↓
return 0
程序正常结束
这就是一个 ROS2 节点程序最基本的生命周期。
15. 一个容易混淆的问题:Publisher 也需要 spin 吗?
分情况。
如果 Publisher 只是:
创建
↓
发一次
↓
结束
理论上不一定非要长期 spin。
但是典型 ROS2 节点通常还会有:
Timer
Subscriber
Service
Action
这些都依赖持续事件处理。
例如"每秒发布一次"的 Publisher,通常会使用:
Timer
↓
spin
↓
Timer到时间
↓
callback
↓
publish
所以实际 ROS2 程序里:
spin(node);
非常常见。
本课压缩成一句话
spin(node)让 ROS2 节点进入持续事件处理状态;消息、Timer、Service 等事件发生后,ROS2 调用对应 callback,处理完后继续等待。
本课小练习
1
callback 为什么不是自己"自动运行"的?
2
spin(node) 最核心的作用是什么?
3
消息处理完成以后,spin() 会直接结束程序吗?
4
下面流程补全:
Publisher
↓
Topic
↓
Subscriber
↓
______
↓
处理消息
5
VLA Controller 同时订阅:
/vla_action
/joint_states
如果 /joint_states 先来了,ROS2 应该优先发生什么?