ROS AsyncSpinner多线程行为验证困惑及原理问询
你遇到的问题其实是ROS订阅者的一个默认行为导致的——默认情况下,同一个订阅者的回调函数是被序列化执行的,哪怕你用了多线程的AsyncSpinner,也不会让同一个订阅的多个回调任务并行运行。
核心原因:ROS的回调序列化机制
ROS的ros::Subscriber默认设置allow_concurrent_callbacks = false,这个参数是用来避免同一个订阅者的消息处理出现线程安全问题(比如你代码里的全局count变量,如果多个线程同时修改会出现竞态)。所以即使你的AsyncSpinner启动了多个线程,当全局回调队列里堆积了同一个订阅者的多个回调任务时,这些任务会被依次取出执行,前一个回调完成后才会启动下一个,这就是你看到每秒只输出一次的原因。
验证并行性的两种可行方案
方案1:开启同一个订阅者的并发回调
如果你确实需要让同一个订阅者的回调并行执行,可以显式修改订阅者选项,开启allow_concurrent_callbacks,同时要注意线程安全(比如对全局变量加锁)。修改后的订阅者代码如下:
#include "ros/ros.h" #include "std_msgs/String.h" #include <boost/thread.hpp> #include <mutex> std::mutex count_mutex; int count = 0; void chatterCallback(const std_msgs::String::ConstPtr& msg) { // 加锁保护全局变量,避免竞态 std::lock_guard<std::mutex> lock(count_mutex); count++; ROS_INFO("Subscriber %i callback: I heard %s", count, msg->data.c_str()); sleep(1); } int main(int argc, char **argv) { ros::init(argc, argv, "subscriber"); ros::NodeHandle nh; // 创建订阅者选项并开启并发回调 ros::SubscriberOptions ops; ops.topic = "chatter"; ops.queue_size = 1000; ops.callback = chatterCallback; ops.allow_concurrent_callbacks = true; // 关键配置 ros::Subscriber sub = nh.subscribe(ops); ros::AsyncSpinner spinner(boost::thread::hardware_concurrency()); spinner.start(); ros::waitForShutdown(); return 0; }
运行这个版本后,你会看到多个回调输出在1秒内同时出现(比如如果你的CPU有4核,会一次看到4条输出),这就证明了AsyncSpinner的多线程并行能力。
方案2:使用多个独立的订阅者
另一种更简单的验证方式是创建多个订阅者监听同一个话题,每个订阅者有自己的回调函数。因为不同订阅者的回调属于不同的任务,AsyncSpinner的多线程会自动并行处理它们:
#include "ros/ros.h" #include "std_msgs/String.h" #include <boost/thread.hpp> void chatterCallback1(const std_msgs::String::ConstPtr& msg) { ROS_INFO("Callback 1: I heard %s", msg->data.c_str()); sleep(1); } void chatterCallback2(const std_msgs::String::ConstPtr& msg) { ROS_INFO("Callback 2: I heard %s", msg->data.c_str()); sleep(1); } int main(int argc, char **argv) { ros::init(argc, argv, "subscriber"); ros::NodeHandle nh; ros::Subscriber sub1 = nh.subscribe("chatter", 1000, chatterCallback1); ros::Subscriber sub2 = nh.subscribe("chatter", 1000, chatterCallback2); ros::AsyncSpinner spinner(boost::thread::hardware_concurrency()); spinner.start(); ros::waitForShutdown(); return 0; }
运行后你会看到Callback 1和Callback 2的输出交替出现(甚至同时出现),说明两个回调在不同线程里并行执行。
总结你的理解偏差
你之前认为AsyncSpinner的多线程会直接并行处理同一个订阅者的所有回调,但实际上ROS默认对单个订阅者的回调做了序列化保护——这是一个安全机制,避免新手写出线程不安全的代码。只有显式开启并发,或者使用多个订阅者,才能看到AsyncSpinner的多线程并行效果。
内容的提问来源于stack exchange,提问作者nebulant

