ROS 搞懂多话题回调机制以及消息队列

ROS 消息队列的运行机制

下面只是自己的理解,如果有误望大家指正!
问:ROS中,publisher和subscriber都有一个消息队列用于数据收发时候的缓存,那么它们的作用分别是什么?
答:ROS话题的通信是异步的,也就是publisher只管不停的发但不管是否被接收,publisher是向Topic的消息队列发布信息,而不是publisher直接push给subscriber,publisher发送数据后,订阅该topic的subscriber则会过来读取,系统会通过位置指针管理不同subscriber读取消息队列的位置,因此可以支持任意多个subscriber对话题进行读取,每个subscriber读取到数据后,会将数据放置到自己的缓存队列上,然后触发注册的回调函数进行处理…
        综上所述,publisher不停将消息发布到消息队列上,直到队列占满,然后新到的消息把最老的消息挤出队列,因此,publisher消息队列的作用就是,缓存一定数量的历史信息,让不能及时订阅的subscriber能读取到之前发布的信息。如果没这个需求或是想让subscriber读取到最新的消息,那么让消息队列长度设为1 。
        而subscriber的消息队列的作用是,将回调函数来不及处理的信息及时的缓存,这样不至于因为回调函数执行或调用过慢,导致数据的丢失。

多话题回调机制

ros::spinOnce()和spin()

spinOnce()的主要用法是:

ros::Rate r(rate);   
while (ros::ok())
{
ros::spinOnce();                
r.sleep();
}

当一个节点订阅了多个话题时,每调用一次ros::spinOnce(),每个接收到消息的subscriber都去执行它的回调函数,那么问题来了,每个subscriber执行回调函数的次数是多少呢?难道每次每个subscriber就调用一次回调函数? 还有多个subscriber的回调函数的执行顺序是什么样呢?
上一节讲到了,subscriber的消息队列用于缓存来不及处理的消息,那么每次调用ros::spinOnce()时,如果各个subscriber只是执行一次回调函数,那么消息队列估计很长时间都不会清0, 所以, 应该说每次调用ros::spinOnce()都会执行与消息队列中缓存的信息数量相同次数的回调函数,只要回调函数执行够快的话,就能清空队列。
另外, 当多个subscriber都需要调用回调函数时,则按顺序依次执行各个subscriber的回调函数。
如下例(非完整):

/*****************************订阅部分*********************************/    
  void chatterCallback1(const std_msgs::String::ConstPtr& msg)
{
 int i=0;
 ROS_INFO("I heard: [%s]", msg->data.c_str());
//  sleep(1);
}

void chatterCallback2(const std_msgs::String::ConstPtr& msg)
{
 ROS_INFO("I heard: [%s]", msg->data.c_str());
 sleep(1);
}
void main()
{
	chatter1_sub = nh.subscribe("/chatter1", 5, chatterCallback1 );
	chatter2_sub = nh.subscribe("/chatter2", 3, chatterCallback2 );
	
	ros::Rate r(0.1);   
	while (ros::ok())
	{
	ros::spinOnce();                
	r.sleep();
	}
}

/******************************发布部分*********************************/
   void send1()
{
   static int t=0;
   std_msgs::String msg;
   std::stringstream ss;
   ss<<"chatter1 "<<t<<endl;
   msg.data = ss.str();
   chatter1_pub.publish(msg);
   t++;
}


void send2()
{
   static int t=0;
   std_msgs::String msg;
   std::stringstream ss;
   ss<<"chatter2 "<<t<<endl;
   msg.data = ss.str();
   chatter2_pub.publish(msg);
   t++;
}
void main()
{
   chatter1_pub = nh.advertise<std_msgs::String>("/chatter1", 1);
   chatter2_pub = nh.advertise<std_msgs::String>("/chatter2", 1);
   ros::Rate r(10);   
   while (ros::ok())
   {
	   send1();
	   send2();
	   ros::spinOnce();                
	   r.sleep();
   }
}

发布器以10HZ的速度发布信息到chatter1,chatter2话题。
接收器,以0.1HZ的速度调用ros::spinOnce(),订阅的消息队列长度分别为5和3,那么以0.1hz即10s的间隔调用ros::spinOnce()时,两个Topic的消息队列都满了,这时一共会调用8次回调函数,且前6次为交替调用 。 有一点要注意,在回调函数里执行的时间也是会算在r.sleep()中的,如上例中,执行chatterCallback2()需要一秒,那么spinOnce()后一共要花3秒在回调函数中,然后的r.sleep()就只会执行7秒的延时,那么可想而知,如果在回调函数中执行的时间大于r.sleep()需要延时的时间,那么回调函数就一直执行下去。
在这里插入图片描述
上图是执行一次spinOnce()所调用的所有回调函数的输出,可见chatter2在执行回调函数里的1S延时操作时,消息队列的数据也更新了,也就是说,执行回调函数时,消息队列并不会被锁定住
另外,在chatter2回调函数延时的时候,chatter1的回调函数也是处于等待当中,就是说这些回调函数都是串行执行的。
而ros::spin()可以看作r.sleep()=0的ros::spinOnce(),即
while (ros::ok())
{
ros::spinOnce();
}
其他方面都可当作spinOnce()来理解。

ros::MultiThreadedSpinner

ros::MultiThreadedSpinner可以实现多线程的回调函数执行,需要和spinner.spin()一起使用,因此是阻塞的, MultiThreadedSpinner的用法是:

  ros::MultiThreadedSpinner spinner(4); // 数字表示调用的线程数  
  spinner.spin(); // spin() will not return until the node has been shutdown

ros::AsyncSpinner

ros::AsyncSpinner 是一种和ros::MultiThreadedSpinner类似的多线程回调函数执行器,不过AsyncSpinner不是阻塞的,因此可以和while()配合去执行一些别的任务,常见的用法如下:

ros::Rate loop_rate(m_frameRate);
  // 使用 ros::AsyncSpinner 可以使这些回调在后台线程中异步地执行,而不是顺序地执行。
  // 这允许你的主线程继续执行其他任务,而不是被回调阻塞。
  ros::AsyncSpinner spinner(m_threadNum);
  spinner.start();
  //当当前节点没有关闭时
  while (ros::ok()) {
	// 可以在这里执行一些别的任务...
	// ...
    ros::spinOnce();
    loop_rate.sleep();   
  }
  //如果当前节点关闭
  rosShutdown(); 

总结:
1、spinOnce()/spin()调用时是串行执行所有的回调函数,并且是各个话题按顺序执行的,执行的次数与调用spinOnce()时消息队列的元素个数相同。
2、MultiThreadedSpinner,ros::AsyncSpinner 调用的回调函数就不再是串行执行的了,而是多线程 并行执行。

  • 74
    点赞
  • 224
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
ROS中,可以使用多线程来处理回调函数ROS提供了两种多线程回调处理的方法:ros::MultiThreadedSpinner和ros::AsyncSpinner。 ros::MultiThreadedSpinner是一个多线程回调处理类,它通过启动指定数量的Spinner线程并发执行Callback队列中的可用回调来处理回调函数。你可以通过指定回调队列来控制线程的数量和执行方式。 ros::AsyncSpinner是另一种多线程回调处理方法,它使用异步的方式来启停Spinner线程。你可以指定要开启的线程数量,并发执行Callback队列中的可用回调。 这两种多线程回调处理方法都能够在处理回调函数时提高效率,充分利用系统资源,同时保证其他回调函数不会被阻塞。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [ROS多线程服务话题定时器等回调函数处理](https://blog.csdn.net/GeForeverr/article/details/108776801)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [【ROS回调多线程问题](https://blog.csdn.net/lemonxiaoxiao/article/details/128422748)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值