0
$\begingroup$

Rosanswers logo

Hello, I have read some tutorials (such as http://www.ros.org/wiki/roscpp/Overview/Callbacks%20and%20Spinning) but I still have some doubts about ROS and callbacks:

Let's say that:

  • in my program I have several threads: A, B and C
  • each thread A,B and C subcribes to a different topic and has declares its own ros::NodeHandle and ros::subscriber
  • each thread has its own main loop where ros::spinOnce() is called and some other stuff is done, such as control loop with ros::Rate loop_rate(Hz)

My questions are:

  1. when ros::spinOnce() is called in each thread, which callbacks are executed?
  2. is there a problem with this configuration? if so, how should it be done?

The reason why I have several threads is for parallel execution of some stuff related to the callbacks, but also to have different control loop speeds: i.e. one thread will run at 10hz while other one will run at 30hz.

Thanks for your time!

EDIT: detail explanation on my case:

thread A: subscribes to laser, callback gets data from laser, thread process the data to detect legs

thread B: subscribes to kinect, callback gets data from kinect, thread process the data to detect humans

thread C: subscribes to a path-publisher (own work), callback gets points, thread computes a route and publish cmd_vel

etc...


Originally posted by Victor_ocv2 on ROS Answers with karma: 483 on 2013-01-22

Post score: 26


Original comments

Comment by Mac on 2013-01-22:
This seems like a complicated use case, and I want to be sure you don't have an XY problem. Can you describe what you're trying to accomplish (the problem itself, that is) in more detail?

Comment by Victor_ocv2 on 2013-01-22:
Detailled information published, thanks for your time!

Comment by Mac on 2013-01-22:
Cool, thanks for the update. How interdependent are A, B, and C here? If the coupling is fairly loose, this looks to me like three nodes, not three threads. If it's fairly tight, that's a different story.

Comment by joq on 2013-01-22:
If you are just worried about message copying overhead, another option is to user nodelets, see: http://ros.org/wiki/nodelet

Comment by Victor_ocv2 on 2013-01-22:
Each thread writes its results in shared memory. These results are read by the main process, which is the robot's state machine

Comment by Victor_ocv2 on 2013-01-22:
I have read about nodelets in the past, but as I understand it requires the creation of several nodes first (extra work). Moreover, as I understand they are indicated for high throughput nodes that need to communicate, which I do not think it is my case (depends on what is high throughput)

$\endgroup$

1 Answer 1

0
$\begingroup$

Rosanswers logo

Briefly: It won't work as you expect and you probably do not need such a complex way of designing your node.

The theory

When it comes to communication in roscpp, two kind of objects are handling callbacks:

A spinner is an object that has the ability to call the callbacks contained in a callback queue. A callback queue is the object in which each subscriber will add an element each time a message is received by resolving which kind of message should call which callbacks (and with which arguments).

Regarding the spinners, there is currently three implementations available in roscpp:

  • Single thread spinner: the default one, takes the messages contained in a callback queue and process the callbacks one by one while blocking the execution of the thread that called it.
  • Multi-threaded spinner: spawns a couple of threads (configurable) that will execute callbacks in parallel when messages are received but blocks the execution of the thread that called it.
  • Asynchronous spinner: spawns a couple of threads (configurable) that will execute callbacks in parallel while not blocking the thread that called it. The start/stop method allows to control when the callbacks start being processed and when it should stop.

These objects may be instantiated manually in advanced ROS nodes but to avoid verbose initialization, an alternative, object-less, API is provided through functions in the ROS namespace. Aka ros::spin(), ros::spinOnce() and so on. This API rely on a default callback queue implemented as a singleton which is accessible through the ros::getGlobalCallbackQueue() function.

So basically when you call the ros::spinOnce() function, a single-thread spinner is created and its spin method is called once using the default callback queue (see init.cpp from roscpp).

And to finish, when you create a Subscriber, you pass it a NodeHandle and each NodeHandle has an associated callbackqueue that default to the global one but which can be overridden using the getCallbackQueue/setCallbackQueue methods.

Your case

If you take a look at spinner.cpp you will see there is a mutex that make the SingleThreader thread-safe while discouraging you to use it in this case (line 48).

Conclusion: what you do is safe but will trigger regularly ROS error messages as there is chances that several instances of ros::SpinOnce will be executed in parallel.

Proposed solution

Considering your applications, I think your callbacks are just not written as they should. A callback should stay as simple and fast as possible. In most of the cases, a callback is just feeding ROS data to your own classes which is usually as simple as copying data (and maybe converting them). It will help you ensuring that your callbacks are thread-safe (if you want to convert your node to a nodelet for instance, one day) and avoid making "ros::Spin" blocking for a long time, even in the case you are using the single-threaded spinner.

Typically, if you want to do time-consuming computations such as "leg detection", the callbacks are definitively not the place to do it.

Just, copy your data to a LegDetector object instance and call in your main thread the method that will do the heavy work. In particular here, you really don't care about losing old messages if your node is not fast enough so there is really no reason to pay the extra-burden of all the multi-thread infrastructure. Use Boost.Bind for instance, to pass a reference to your LegDetector class to the callback that will just copy the relevant laser data into it.

If, at some point, you need to synchronise the received data, use the message_filters API. In this case again, trying to use multi-thread to do so is definitively a bad idea.


Originally posted by Thomas with karma: 4478 on 2013-01-22

This answer was ACCEPTED on the original site

Post score: 79


Original comments

Comment by Victor_ocv2 on 2013-01-23:
Exactly the answer I was looking for. Just for the record I did not to get any ros error msgs (spinner.cpp line 48) about what I was doing, therefore It was unclear for me to notice if I was doing something strange or not. Moreover, I thank you for this clear explanation on ros callbacks and spins

Comment by brice rebsamen on 2013-04-02:
+1 for the clear and interesting answer. I am facing a similar problem. I don't get the "main thread" part. should it be while(ros::ok()) { leg_detector.process(latest_scan); ros::spinOnce(); } ? I think there should be at least 2 threads: one for the callbacks and one for the heavy work...

Comment by Thomas on 2013-04-02:
Yes, this is what I suggest. The question is: what benefit will you have if create two threads? Your leg detector is anyway too slow to process all the data. 99% of the cases you are just fine letting them being thrown away.

Comment by McMurdo on 2014-01-08:
This is an excellent answer! It is even clearer than the tutorials in some places. Brilliant! Thank you!

Comment by haryngod on 2015-12-29:
I have similar situation. I want subscribe scan data and image_rect data from xtion. so I already use message_fillters. but it seem not working. I think is that sync problem maybe. I ask here.

Comment by José Diôgo on 2016-11-30:
Work perfectly here! thank you!

Comment by doncat on 2018-05-31:
How can we add this to the Callbacks and Spinning wiki page? This would be very helpful to have and would clear up unnecessary confusion when visiting that page for the first time.

Comment by prex on 2020-12-08:
I don't see the advantage of calling the time-consuming computation in the same main thread. Every callback is blocked anyway, since ros::spin() is not executed. Or am I misunderstanding something?

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.