ROS1节点到底有几个线程

阅读ROS1 noetic源码ros_comm,得到如下结论:

一、rospy

python版本的ROS1节点会给每个subscriber,每个timer新建一个线程。所以python ROS节点根本不需要rospy.spin()来执行回调,因为subscriber和timer的回调都在各自的线程里运行,rospy.spin()的作用只是阻塞主线程保证程序不退出罢了。

但要注意多线程并不意味着并发,python由于全局解释锁(GIL)的存在,不管其进程中开了几条“线程”,仍然实际只占用了系统的一条线程,不会利用cpu的多核并发性能。所以python的线程在并发角度其实时假线程。要在一个程序内实现真并发,可以用multi-process库,实际上也是开多个进程。

rospy由于每个subsciber和timer的回调都在各自单独的线程中执行,所以并不是线程安全的。也就是说你需要手动为不同回调函数中同时用到的变量进行加锁等操作来保证线程安全。好在有一些python的原子操作是线程安全的,比如读写单一基本变量(int, float, string)。

参考:Threads in ROS and Python

二、roscpp

c++版本的ROS1节点要复杂得多。roscpp建立了一个global callback queue回调队列,所有的subscriber和timer都将自己的回调add到这个callback queue上(除非你为这个subscriber或者timer新建并指定了额外的callback queue)。global callback queue一般由ros::spinOnce()或者ros::spin()单线程来执行,除非你用了ros::MultiThreadedSpinner或者AsyncSpinner,此时callback queue是由多条线程来抢着执行的。

由此可见,如果你用单线程的ros::spin(),那么roscpp节点的回调函数原生就是线程安全的,因为这些回调虽然产生时是异步的,但实际在callback queue上执行时还是按照add的先后顺序来,不存在线程竞争,因此不需要在各个回调函数里做加锁等线程安全处理。当然如果你用ros::MultiThreadedSpinner或者AsyncSpinner,那还是得自己加锁的。

还可以看出,roscpp的回调并不是实时执行的,而是在callback queue中排队等着被执行,某个过长的回调也会阻塞其他回调函数。这也是ROS1被广为诟病的一点。我们在timer和subscriber的回调函数中不应当添加需要过长执行时间的代码,尤其是少用不用sleep函数。即使你用ros::MultiThreadedSpinner或者AsyncSpinner,开的线程不够多的话也不保证回调不阻塞。

其实除了执行回调队列的线程,ros cpp节点中还有几个功能性的线程。我能发现的有:

  • 主线程

也就是int main()开始的那个线程,如果你习惯在while循环中用rate.sleep和ros::spinOnce(),那么其实你的主程序主要就是在这个主线程执行。但这种写法很不推荐,因为回调线程执行回调的频率取决于你ros::spinOnce()的频率,往往是不及时的。更好的做法是用timer来执行主要的功能函数,主线程里就只ros::spin(),它是按照最快的速度来执行回调队列中的回调函数。

  • PollManager线程

PollManager在ros init时启动,里面包含一个掌管subscriber、publisher、service的socket通信的线程,以最大频率遍历执行所有socket操作( pollset_.update() ),包括:subscriber的socket非阻塞recv消息(并把收到的消息和回调push到callback queue上),publisher的socket send消息,service的send和recv。PollManager线程里还以signal信号槽的方式顺带执行一些ConnectionManager、ServiceManager、TopicManager和话题、连接有关的任务。

  • TimerManager线程

TimerManager是全局静态的,在有createTimer的时候初始化,并掌管进程内所有timer的callback,它会通过schedule来安排所有timer callback的先后顺序,并add到callback queue上。

  • internalCallbackQueue

不同于主线程里ros::spinOnce()和ros::spin()来执行所有的回调,/clock话题的回调是内部单独开的线程来处理。因为/clock话题作为ROS仿真时间的依据,它的更新必须是最及时的,不能受到其他回调阻塞的影响。

  • AsyncSpinner

ros::MultiThreadedSpinner也是新建了多个AsyncSpinner。在AsyncSpinner.start()的时候会新开线程来处理回调。

你可能感兴趣的:(ros,c++,笔记,c++,机器人,python,ros)