0
$\begingroup$

When looking at the examples it's unclear if ROS2 nodes are thread safe if you want to launch your own threads vs using multithread-executor.

For example, suppose I have a process I want to run multiple nodes on. I want to give each node its own thread.

Can I launch each node with singlethread-executor using Pthreads? Looking at the examples at the start of main it appears some global information is set vs being localized to each thread so it was unclear to me if it would be safe to do the above.

$\endgroup$

2 Answers 2

1
$\begingroup$

At least in Python, I was running into issues attempting the strategy you described (manually creating a thread which called spin(node) for each node), with the solution being a MultiThreadedExecutor to which you add the different nodes. The executor can spin in a different thread without issue.

rclpy.init(args)

node1 = Node1()
node2 = Node2()

executor = rclpy.executors.MultiThreadedExecutor()
executor.add_node(node1)
executor.add_node(node2)

et = Thread(target=executor.spin)
et.start()
et.join()
node1.destroy_node()
node2.destroy_node()
rclpy.shutdown()

I haven't tested it, but it is possible that manually creating a series of SingleThreadedExecutor and spinning those in separate threads would work better than simply calling spin(node) in different threads.

Documentation on Executors: https://docs.ros.org/en/iron/Concepts/Intermediate/About-Executors.html?highlight=executors

$\endgroup$
1
  • $\begingroup$ I checked this, and it turns out that rclpy differs from rclcpp wrt the spin() function. In rclcpp, each call of spin() instantiates a SingleThreadedExecutor, see here, whereas in the spin() of rclpy a global executor is retrieved, see here. $\endgroup$
    – JRTG
    Commented Dec 14, 2023 at 8:19
1
$\begingroup$

By default, ROS 2 is thread safe:

  • Each node's callbacks are called mutually exclusive (i.e. only one callback per node is called at a time, there is no concurrence within a node),

  • Different nodes' callbacks can be executed concurrently:

    • If they run in different processes, or
    • If they are in the same process and spun by a MultiThreadedExecutor, or
    • If they are in the same process and you use multiple threads each running a SingleThreadedExecutor,

    But even in those cases, the active callbacks cannot access another nodes' data, and only one callback per node is being executed, so there are no concurrency issues.

However:

If you want to execute multiple callbacks of the same node concurrently, you need to assign the callbacks of that node to callback groups:

  • Either assign them to a Reentrant callback group,
  • Or: use (at least) two MutuallyExclusive callback groups.

Obviously in those cases you need to make sure that different callbacks access the same variables in a thread-safe way.

See this previous answer for more info.

Wrt. spawning your own threads:

I don't think it makes sense to spawn your own threads to run multiple SingleThreadedExecutors, as that's what the MultiThreadedExecutor does for you.

However: depending on your application, it might be computationallly more efficient to spawn multiple threads each running a StaticSingleThreadedExecutor.

For completeness: recently the EventsExecutor was also added; might be relevant to also look at its pros and cons (and report on those here in a new post! ;-)).

$\endgroup$
3
  • $\begingroup$ Thanks I'm still a little confused on when you can launch a thread to run a node, is it OK to launch a thread via the function call spin? Or should a thread function create a separate executor in the given thread first? This was my confusion. I know when following the examples init is called first which indicates some global information is set. $\endgroup$ Commented Dec 14, 2023 at 16:03
  • $\begingroup$ I don't understand what you mean, if you have further questions it is best to add them to your original question, or to post a new question if it is a separate issue. Wrt. spin however: this is just a convenience function that creates a SinglethreadedExecutor, adds the node and calls spin in the executor. $\endgroup$
    – JRTG
    Commented Dec 14, 2023 at 18:00
  • $\begingroup$ Basically, it's not clear how the executor object interacts with the process, ex if spooling up two executor threads would cause a crash because they are both using some resources global to the process. $\endgroup$ Commented Dec 14, 2023 at 23:19

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.