ROS中的多线程使用

ROS里面每个节点都是用默认的单线程来处理任务,以前很多时候都没有注意使用多线程的方式来处理回调函数,今天在这里总结一下。实际上ROS中提供了多线程接口,因为单线程在很多时候不能保证时效性,所以我们往往还会使用到多线程。为了文章的完整性,下面将讨论单线程和多线程下的情况。

单线程

image-20221005204110888

在只有一个Spinner thread的情况下,callback queue只能顺序执行。假设场景:一个发布节点 publisher 向两个话题(/topic/Atopic/b )以1Hz速度发布消息,另外一个节点 subscriber 接收两个话题上的消息,以回调函数的方式处理消息内容,其中 CallbackA 在打印接收到的消息以外还会sleep 2s,CallbackB 只打印接收到的消息。代码如下

publisher.cpp

#include "ros/ros.h" 
#include "std_msgs/String.h"   
#include <sstream>   
 
int main(int argc, char **argv)
{
  ros::init(argc, argv, "publisher");  
  ros::NodeHandle n;   
  ros::Publisher pub_a = n.advertise<std_msgs::String>("/topic/A", 1000);
  ros::Publisher pub_b = n.advertise<std_msgs::String>("/topic/B", 1000);   
  ros::Rate loop_rate(1);
  std_msgs::String msg;   
  int count = 0;
  while (ros::ok())
  {
    std::stringstream msg_a;
    msg_a << "msg_a " << count;
    msg.data = msg_a.str();
    ROS_INFO("%s", msg.data.c_str());  
    pub_a.publish(msg);
      
    std::stringstream msg_b;
    msg_b << "msg_b " << count;
    msg.data = msg_b.str();
    ROS_INFO("%s", msg.data.c_str());  
    pub_b.publish(msg);
      
    ros::spinOnce();
    loop_rate.sleep();
    ++count;
  }
  return 0;
}

subscriber.cpp

#include "ros/ros.h"
#include "std_msgs/String.h"
#include <thread>

void CallbackA(const std_msgs::String::ConstPtr& msg)  
{
  ROS_INFO("A heard: [%s]", msg->data.c_str());
  std::this_thread::sleep_for(std::chrono::seconds(2));
}

void CallbackB(const std_msgs::String::ConstPtr& msg)  
{
  ROS_INFO("B heard: [%s]", msg->data.c_str());
}
 
int main(int argc, char **argv)
{
  ros::init(argc, argv, "subscriber");
  ros::NodeHandle n;
  ros::Subscriber sub_a = n.subscribe("/topic/A", 1000, CallbackA);
  ros::Subscriber sub_b = n.subscribe("/topic/B", 1000, CallbackB);
  ros::spin();
  return 0;
}

输出结果

image-20221005193446564

从执行效果中可以看到,不管有多少个Subscriber,节点都只能顺序执行回调,在对实时性要求比较高的场景中,这种方式会使得系统性能大打折扣,因此需要使用多线程的方式

多线程

订阅多个Topic,多个Spinner threads

image-20221005204326576

在刚刚的 subscriber.cpp 代码中增加如下两行即可

image-20221005195124035

输出结果

image-20221005195200435

可以看到,A和B两个回调函数现在分别在两个不同的线程中进行处理,B回调不会因为A线程的sleep而被阻塞了。

订阅一个Topic,多个Spinner threads

image-20221005204359764

假设只有一个Topic, 发布端的频率比较高,但我们希望尽可能多地响应消息,这时可以把回调任务分发给多个线程处理

修改 subscriber.cpp

image-20221005200954186

输出结果如下

image-20221005200908064

可以看到,回调A的处理频率和发布频率一致,都是1Hz。如果是单线程处理回调,那么回调的频率应该是0.5Hz,因为CallbackA()中sleep 2s

订阅多个Topic,每个Subscriber一个Callback queue

image-20221005204429264

上面提到的两种情况都是只有一个回调队列,这种情况会导致在处理回调B的时候无法处理回调A。ROS里面还提供了给每个回调创建一个回调消息队列,这样可以实现真正的多线程回调

修改 subscriber.cpp 如下:

image-20221005203113228

输出结果:

image-20221005203212443

给每一个subscriber创建一个单独的callback queue,这样就解决了即使用了MultiThreadedSpinner但所有的callback依然在同一个queue执行的问题,此方法用来解决subscriber的优先级问题。

参考:https://zhuanlan.zhihu.com/p/375418691

猜你喜欢

转载自blog.csdn.net/weixin_40599145/article/details/127176557
今日推荐