0
$\begingroup$

Rosanswers logo

Is it correct to assume that a new thread will be used for every instance of every subscription and service callback?


Originally posted by bhaskara on ROS Answers with karma: 1479 on 2011-03-23

Post score: 11

$\endgroup$

3 Answers 3

0
$\begingroup$

Rosanswers logo

For a particular topic, there is only one thread that all the subscribers share.

Each service callback does get its own thread because there can only be one service callback for a particular service.

Future versions of rospy will likely have a different, more versatile threading model for subscriptions.


Originally posted by kwc with karma: 12244 on 2011-03-24

This answer was ACCEPTED on the original site

Post score: 8


Original comments

Comment by tfoote on 2011-03-26:
What @kwc is describing is the callbacks.

Comment by kwc on 2011-03-24:
Yes, there is only one thread for a subscription, which includes the work of doing all the callbacks.

Comment by Patrick Bouffard on 2011-03-24:
Thanks. What about the callbacks? I had the impression that those were executed serially from a single queue for all subscribers.

Comment by kwc on 2011-03-24:
For each subscribed topic in a node, there is a thread. 5 subscribed topics, 5 threads. If you have two subscriptions to one topic, one thread. Publishers are synchronous/blocking, so the publishing occurs in the same thread. (the i/o engine of rospy is overdue for a rewrite)

Comment by Patrick Bouffard on 2011-03-24:
Ken, did you mean 'for a particular node ( or maybe nodehandle?), there is only one thread...'? If I have a python node that subscribes to 5 topics how many threads are in play? What about on the publishing node's side?

Comment by kwc on 2011-03-24:
Each service has a single, dedicated thread that handles the underlying transport and calling into the callback. Service requests are processed serially in the order they come off the socket.

Comment by bhaskara on 2011-03-24:
OK. For services, does it use a threadpool or spin up a new thread on each request?

Comment by Seanny123 on 2013-12-09:
The detailed replies have been great, but just to eliminate all ambiguity, from what I understand this also means that my script will wait for my callback to end before starting another callback, even if a subscription releases a new message. Is this correct?

Comment by paulbovbel on 2016-09-15:
Just to clarify, after finding something suprising - while one thread will serially run all subscription callbacks, services get a separate thread for each connection (https://github.com/ros/ros_comm/blob/kinetic-devel/clients/rospy/src/rospy/impl/tcpros_service.py#L247), thus one per client.

$\endgroup$
0
$\begingroup$

Rosanswers logo

The accepted answer is not correct, at least with Kinetic. Consider the following:

#!/usr/bin/env python

import threading

import rospy
from std_msgs.msg import Empty

global i
i = 0


def callback(_):
    global i
    i += 1
    j = i
    while not rospy.is_shutdown():
        print('sleeping: %d, thread: %s' % (j, threading.current_thread()))
        rospy.Rate(1).sleep()
    print('exiting loop for %d: %s' % (j, rospy.is_shutdown()))


def main():
    rospy.init_node('foooo')
    sub = rospy.Subscriber('foo', Empty, callback, queue_size=1)


if __name__ == '__main__':
    main()
    rospy.spin()

Then, call rostopic twice by running the following command twice in parallel in different shell sessions:

rostopic pub /foo std_msgs/Empty "{}"

Observe the following output:

sleeping: 1, thread: <Thread(/foo, started daemon 140177158719232)>
sleeping: 2, thread: <Thread(/foo, started daemon 140177150326528)>
sleeping: 1, thread: <Thread(/foo, started daemon 140177158719232)>
sleeping: 2, thread: <Thread(/foo, started daemon 140177150326528)>
sleeping: 1, thread: <Thread(/foo, started daemon 140177158719232)>
sleeping: 2, thread: <Thread(/foo, started daemon 140177150326528)>
sleeping: 1, thread: <Thread(/foo, started daemon 140177158719232)>
sleeping: 2, thread: <Thread(/foo, started daemon 140177150326528)>
sleeping: 1, thread: <Thread(/foo, started daemon 140177158719232)>
sleeping: 2, thread: <Thread(/foo, started daemon 140177150326528)>
sleeping: 1, thread: <Thread(/foo, started daemon 140177158719232)>
sleeping: 2, thread: <Thread(/foo, started daemon 140177150326528)>

Originally posted by rsinnet with karma: 101 on 2018-01-25

This answer was NOT ACCEPTED on the original site

Post score: 10


Original comments

Comment by [email protected] on 2018-07-13:
You are right.

Comment by pitosalas on 2023-02-05:
I don't quite understand why the observed output demonstrates that the accepted answer is incorrect. Of course time may have passed and those two comments are out of sync.

$\endgroup$
0
$\begingroup$

Rosanswers logo

It actually seems like a subscriber process has one thread per topic subscribed per publisher. So if node A subscribes to topic /foo and node B publishes to topic /foo and node C also publishes to topic /foo, node A will have 2 callback threads.

ros_thread_sub.py

#!/usr/bin/env python

import threading

import rospy
from std_msgs.msg import String

def callback(msg):
    print("Msg from %s on thread %s" % (msg.data, threading.current_thread()))

def main():
    rospy.init_node('foooo')
    rospy.Subscriber('foo', String, callback, queue_size=1)
    rospy.spin()

if __name__ == '__main__':
    main()

ros_thread_pub.py (starting publisher B and C in separate terminals)

#!/usr/bin/env python

import threading
import sys

import rospy
from std_msgs.msg import String

global proc_id
proc_id = ""

def publisher():
    msg = String(proc_id)
    r = rospy.Rate(1)
    pub = rospy.Publisher('foo', String, queue_size=1)
    while not rospy.is_shutdown():
        print("publishing")
        pub.publish(msg)
        r.sleep()

def main():
    global proc_id
    proc_id = sys.argv[1]
    rospy.init_node(proc_id)
    thread = threading.Thread(target=publisher)
    thread.start()
    rospy.spin()

if __name__ == '__main__':
    main()

Console output:

Msg from B on thread <Thread(/foo, started daemon 140237958694656)>
Msg from B on thread <Thread(/foo, started daemon 140237958694656)>
Msg from B on thread <Thread(/foo, started daemon 140237958694656)>
Msg from B on thread <Thread(/foo, started daemon 140237958694656)>
Msg from C on thread <Thread(/foo, started daemon 140237950301952)>
Msg from B on thread <Thread(/foo, started daemon 140237958694656)>
Msg from C on thread <Thread(/foo, started daemon 140237950301952)>
Msg from B on thread <Thread(/foo, started daemon 140237958694656)>
Msg from C on thread <Thread(/foo, started daemon 140237950301952)>
Msg from B on thread <Thread(/foo, started daemon 140237958694656)>

Originally posted by zacwitte with karma: 170 on 2018-04-18

This answer was NOT ACCEPTED on the original site

Post score: 8

$\endgroup$

Your Answer

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