Manage how the callbaks are called: Exeturos & CallbackGroups & Qos#

Exeturos#

  • responsible for managing the execution of callbacks for a set of subscriptions, services, and timers.

CallbackGroups#

  • organizing the callbacks of a node in groups.

  • The callback group must be stored throughout execution of the node (eg. as a class member), or otherwise the executor won’t be able to trigger the callbacks.

  • Callbacks of different callback groups may always be executed in parallel. The Multi-Threaded Executor uses its threads as a pool to process as many callbacks as possible in parallel according to these conditions. For tips on how to use callback groups efficiently, see Using Callback Groups.

Qos#

  • Quality of Service

Relationship between Executors and CallbackGroups#

CallbackGroups act as the config options for Executors.

For example

  • priority of the callback group

Types of Executors#

SingleThreadedExecutor(Default)#

int main(int argc, char* argv[])
{
   // Some initialization.
   rclcpp::init(argc, argv);
   ...

   // Instantiate a node.
   rclcpp::Node::SharedPtr node = ...

   // Run the executor.
   rclcpp::spin(node);

   // Shutdown and exit.
   ...
   return 0;
}

is equivalent to:

int main(int argc, char* argv[])
{
   // Some initialization.
   rclcpp::init(argc, argv);
   ...

   // Instantiate a node.
   rclcpp::Node::SharedPtr node = ...

   // Instantiate a single-threaded executor.
   rclcpp::executors::SingleThreadedExecutor executor;

   // Add the node to the executor.
   executor.add_node(node);

   // Run the executor.
   executor.spin();

   // Shutdown and exit.
   ...
   return 0;
}

The call to spin(node) basically expands to an instantiation and invocation of the Single-Threaded Executor.

  • used by the container process for components, i.e. in all cases where nodes are created and executed without an explicit main function.

MultiThreadedExecutor#

  • number of threads can be specified when creating the executor.

int main(int argc, char* argv[])
{
   // Some initialization.
   rclcpp::init(argc, argv);
   ...

   // Instantiate a node.
   rclcpp::Node::SharedPtr node = ...

   // Instantiate a multi-threaded executor with 4 threads.
   rclcpp::executors::MultiThreadedExecutor executor(4);

   // Add the node to the executor.
   executor.add_node(node);

   // Run the executor.
   executor.spin();

   // Shutdown and exit.
   ...
   return 0;
}

StaticSingleThreadedExecutor#

  • used in the case where you want to create a single-threaded executor that is not associated with a node.

int main(int argc, char* argv[])
{
   // Some initialization.
   rclcpp::init(argc, argv);
   ...

   // Instantiate a single-threaded executor.
   rclcpp::executors::StaticSingleThreadedExecutor executor;

   // Run the executor.
   executor.spin();

   // Shutdown and exit.
   ...
   return 0;
}

Types of CallbackGroups#

Mutually exclusive#

  • Callbacks of this group must not be executed in parallel.

Reentrant#

  • Callbacks of this group may be executed in parallel.

Come All Together#

Senario 1: if messages arrive faster than the callbacks can process them#

  • SingleThreadedExecutor:

    • Sequential message processing.

    • Potential backlog if callbacks are slow.

    • Callback groups and QoS influence execution order and message handling characteristics.

  • MultiThreadedExecutor:

    • Concurrent message processing.

    • Reduced backlog but potential synchronization overhead.

    • Callback groups and QoS still influence execution and message handling but with added concurrency considerations.

Senario 2: create a executor that take the latest message and execute a time-consuming callback sequentially#

int main(int argc, char* argv[])
{
   // Some initialization.
   rclcpp::init(argc, argv);
   ...

   // Instantiate a node.
   rclcpp::Node::SharedPtr node = ...

   // Instantiate a single-threaded executor.
   rclcpp::executors::SingleThreadedExecutor executor;

   // Add the node to the executor.
   executor.add_node(node);

   // Create a subscription to a topic.
   rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr subscription = ...

   // Create a callback group.
   rclcpp::CallbackGroup::SharedPtr callback_group = node->create_callback_group(
      rclcpp::CallbackGroupType::MutuallyExclusive);
   rclcpp::SubscriptionOptions options;
   options.callback_group = callback_group;

   // Create a subscription to a topic with the callback group.
   rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr subscription = node->create_subscription<sensor_msgs::msg::Image>(
      "topic", rclcpp::SensorDataQoS(), std::bind(&callback, std::placeholders::_1), options);

   // Run the executor.
   executor.spin();

   // Shutdown and exit.
   ...
   return 0;
}
  • ‘10’ is the queue size of the subscription.

  • ‘rmw_qos_profile_sensor_data’ is the QoS profile of the subscription. The main characteristic of this profile is that it is transient local, meaning that the messages are not stored if there are no subscribers.

** ANSWERS FOLLOWING IS GENERATED BY GPT **

  • Q: which msg dose the callback function take when the last msg was processed, the latest in the queue or the oldest?

  • A: The callback function will process the oldest message in the queue first.

  • Q: how to make the callback function take the latest message in the queue?

  • A: change the queue size from 10 to 1.

References#

https://docs.ros.org/en/humble/Concepts/Intermediate/About-Executors.html

https://docs.ros.org/en/humble/How-To-Guides/Using-callback-groups.html

https://docs.ros.org/en/humble/Concepts/Intermediate/About-Quality-of-Service-Settings.html