spin与spinOnce

在话题发布和订阅中,消息订阅器一旦知道话题上面有消息发布,就会将消息的值作为参数传入回调函数中,把回调函数放到了一个回调函数队列中,它们的函数名一样,只是实参不一样,这就是subscribe函数的作用。但是此时还没有执行callback函数,当spinOnce函数被调用时,spinOnce就会调用回调函数队列中第一个callback函数,此时回调函数才被执行,然后等到下次spinOnce函数又被调用时,回调函数队列中第二个回调函数就会被调用,以此类推。

注意:因为回调函数队列的长度是有限的,如果发布数据的速度太快,spinOnce函数调用的频率太少,就会导致队列溢出,一些回调函数就会被挤掉,导致没被执行。

对于spin函数,一旦进入spin函数,它就不会返回了,相当于它在自己的函数里阻塞。只要回调函数队列里面有回调函数在,它就会马上去执行。如果没有的话,它就会阻塞,不会占用CPU。

spin()的目的是启动一个新的线程去获取队列中的回调函数并调用它,而回调函数本身不是线程 ,有单线程,同步多线程和异步多线程等情况,这些都有内置的语句。所有用户的调用程序将从 ros::spin()开始调用,只到节点关闭,ros::spin()才有返回值。

ros::spin其实就相当于

1
2
3
4
5
while(ros::ok())
{
// Do Something
ros::spinOnce();
}

发布和订阅话题都不一定要使用spinOnce(),如果仅仅只是响应topic,就用ros::spin(),当程序中除了响应回调函数还有其他重复性工作的时候,那就在循环中做那些工作,然后调用ros::spinOnce()

spinOnece的注意事项

我仔细试验了这几个参数,没有发现缺失回调函数的情况,一般不需要太注意

ros::spinOnce()的用法相对来说很灵活,但往往需要考虑调用消息的时机,调用频率,以及消息池的大小。

比如下面的程序,消息送达频率为10Hz,ros::spinOnce()的调用频率为5Hz,那么消息池的大小就一定要大于2,才能保证数据不丢失,无延迟,跟这里的发布消息池容量无关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//发送频率为10Hz(1秒发10次)  消息池最大容量1000。
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
ros::Rate loop_rate(10);
while (ros::ok())
{
// 发布消息
}


ros::Subscriber sub = n.subscribe("chatter", 2, chatterCallback);
ros::Rate loop_rate(5);
while (ros::ok())
{
/*...TODO...*/
ros::spinOnce();
loop_rate.sleep();
}

看一下源码:

1
2
3
4
void spinOnce()
{
g_global_queue->callAvailable(ros::WallDuration());
}

调用队列中的所有回调函数,如果一个回调还没有准备好调用,再推回队列