ROS多线程订阅问题【转载】
一、传统的多线程发布和订阅程序
我们学习ROS的话题订阅和发布时,都是以一个话题的发布和订阅为基础进行学习的,这里博主以两个话题的发布和订阅进行说明,程序如下:
1.mulit_topic_pub.cpp
//包含ros相关的API头文件
#include "ros/ros.h"
//包含在std_msgs库里的String类型
#include "std_msgs/String.h"
#include <sstream>
int main(int argc, char **argv)
{
ros::init(argc, argv, "multi_pub");
ros::NodeHandle n;
//创建发布者,消息类型为std_msgs::String,队列长度为1
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter1", 1);
ros::Publisher pub2 = n.advertise<std_msgs::String>("chatter2", 1);
ros::Rate loop_rate(10);//10HZ
int count = 0;
while (ros::ok())
{
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
std_msgs::String msg2;
std::stringstream ss2;
ss2 << "hello " << count;
msg2.data = ss2.str();
ROS_INFO("%s", msg.data.c_str());
ROS_INFO("%s", msg2.data.c_str());
chatter_pub.publish(msg);
pub2.publish(msg2);
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
2.multi_topic_sub.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
class multiReceiver
{
public:
multiReceiver()
{
sub = nh.subscribe("chatter1", 1, &multiReceiver::chatterCallback1,this);
sub2 = nh.subscribe("chatter2", 1, &multiReceiver::chatterCallback2,this);
}
void chatterCallback1(const std_msgs::String::ConstPtr& msg);
void chatterCallback2(const std_msgs::String::ConstPtr& msg);
private:
ros::NodeHandle nh;
ros::Subscriber sub;
ros::Subscriber sub2;
};
void multiReceiver::chatterCallback1(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
ros::Rate loop_rate(0.5);//block chatterCallback2()
loop_rate.sleep();
}
void multiReceiver::chatterCallback2(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "multi_sub");
multiReceiver recOb;
ros::spin();
return 0;
}
对于一些只订阅一个话题的简单节点来说,我们使用ros::spin()进入接收循环,每当有订阅的话题发布时,进入回调函数接收和处理消息数据。但是更多的时候,一个节点往往要接收和处理不同来源的数据,并且这些数据的产生频率也各不相同,当我们在一个回调函数里耗费太多时间时,会导致其他回调函数被阻塞,导致数据丢失。这种场合需要给一个节点开辟多个线程,保证数据流的畅通。
以10hz的频率发布了chatter1和chatter2两个话题,在订阅程序中,回调函数1中加入了2s的延时,导致了回调函数2也只能2s才能接收到一个数据,为了是回调函数2能正常接收数据,为此我们要探究以下多线程控制的相关技术。
二、多线程函数介绍
首先,在多线程编程中,有两个常用的多线程函数,分别是ros::MultiThreadedSpinner和ros::AsyncSpinner,他们可以在一个节点中开辟多个线程,这里博主参考的是官方的多线程教程中的相关介绍,链接如下:
链接: [http://wiki.ros.org/roscpp/Overview/Callbacks and Spinning](http://wiki.ros.org/roscpp/Overview/Callbacks and Spinning).
1.ros::MultiThreadedSpinner
ros::MultiThreadedSpinner spinner(4); // Use 4 threads
spinner.spin(); // spin() will not return until the node has been shutdown
2.ros::AsyncSpinner
ros::AsyncSpinner spinner(4); // Use 4 threads
spinner.start();
ros::waitForShutdown();
三、多线程发布和订阅程序
在原有mulit_topic_sub.cpp文件的基础上进行修改,修改如下,multi_thread_sub.cpp文件是根据ros::MultiThreadedSpinner函数进行修改的,multi_thread_sub2.cpp文件是根据ros::AsyncSpinner函数进行修改的
1.mulit_thread_sub.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <boost/thread.hpp>
class multiThreadListener
{
public:
multiThreadListener()
{
sub = n.subscribe("chatter1", 1, &multiThreadListener::chatterCallback1,this);
sub2 = n.subscribe("chatter2", 1, &multiThreadListener::chatterCallback2,this);
}
void chatterCallback1(const std_msgs::String::ConstPtr& msg);
void chatterCallback2(const std_msgs::String::ConstPtr& msg);
private:
ros::NodeHandle n;
ros::Subscriber sub;
ros::Subscriber sub2;
};
void multiThreadListener::chatterCallback1(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
ros::Rate loop_rate(0.5);//block chatterCallback2()
loop_rate.sleep();
}
void multiThreadListener::chatterCallback2(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "multi_sub");
multiThreadListener listener_obj;
ros::MultiThreadedSpinner s(2);
ros::spin(s);
return 0;
}
2.mulit_thread_sub2.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <boost/thread.hpp>
class multiThreadListener
{
public:
multiThreadListener()
{
sub = n.subscribe("chatter1", 1, &multiThreadListener::chatterCallback1,this);
sub2 = n.subscribe("chatter2", 1, &multiThreadListener::chatterCallback2,this);
}
void chatterCallback1(const std_msgs::String::ConstPtr& msg);
void chatterCallback2(const std_msgs::String::ConstPtr& msg);
private:
ros::NodeHandle n;
ros::Subscriber sub;
ros::Subscriber sub2;
};
void multiThreadListener::chatterCallback1(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
ros::Rate loop_rate(0.5);//block chatterCallback2()
loop_rate.sleep();
}
void multiThreadListener::chatterCallback2(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "multi_sub");
multiThreadListener listener_obj;
ros::AsyncSpinner spinner(2); // Use 2 threads
spinner.start();
ros::waitForShutdown();
return 0;
}
代码运行效果一致,可以很清楚的看到,两个订阅者之间并没有相互阻塞,同时完成了对话题的订阅和信息打印
相比较之下,AsyncSpinner比MultiThreadedSpinner更优,它有start() 和stop() 函数,并且在销毁的时候会自动停止,这样我们就完成了ubuntu16.04下多话题订阅和发布的目的了
四、Python中如何解决该问题
在python中测试时,发现并不需要多线程,猜测时rospy应该为每个回调函数自动添加了多线程操作,所以在python中订阅同时订阅多个话题就可以了。可以忽略上文中C++的类似处理。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现