NC / HOME FEATURES

ROS 2 Executors Turn Robot Responsiveness Into a Scheduling Problem

ROS 2 executors decide when callbacks run, making thread count, callback groups, and workload length part of robot design.

ROS 2 Jazzy robot artwork for an explainer about executor scheduling
Image: Open Robotics, via ROS

A robot can have fast sensors, capable actuators, and a good controller, then still feel sluggish because its software is waiting in the wrong queue. In ROS 2, the executor is the part of the system that decides when callbacks run, so scheduling is not an implementation detail. It is part of the robot’s behavior.

The ROS 2 executor documentation describes an executor as the mechanism that waits for work and invokes callbacks for subscriptions, timers, services, and other entities. That sounds modest, but those callbacks are where a robot receives sensor data, updates state, answers requests, and sends commands. Every choice about how they share execution time changes what the machine can respond to and how quickly it can respond.

One thread, clear trade-offs

A single-threaded executor runs callbacks through one thread. The model is easy to reason about: callbacks do not run simultaneously, shared state has fewer concurrency hazards, and the order of work is easier to inspect. The cost is equally clear. A long callback can hold up unrelated work, so a slow image transform or service request can delay a timer that was meant to keep a control loop moving.

A multi-threaded executor gives the system more room to overlap work, but adding threads does not automatically make a robot responsive. The available threads still have to be scheduled, and callbacks still need rules about which work may run together. More concurrency can reduce waiting in one part of a graph while introducing contention, races, or harder-to-reproduce timing in another.

Callback groups are the guardrails

ROS 2 callback groups let a node describe those concurrency rules. A mutually exclusive group keeps its callbacks from executing at the same time. A reentrant group allows callbacks to overlap when the executor and the available threads can support it. That gives developers a middle ground between putting everything behind one lock and letting every callback compete with every other callback.

The useful design question is not “How many threads can we add?” It is “Which work is allowed to overlap?” Sensor callbacks that only copy data may be good candidates for a different group from a state update that must see a consistent snapshot. A service that performs heavy planning may need a separate path from the timer that keeps low-level motion alive. The group structure makes those intentions visible in the node.

Short callbacks make better robots

Executor tuning cannot rescue a callback that tries to do an entire pipeline in one turn. Long-running work should be measured, split where that is safe, or moved behind a queue that gives the executor back its thread quickly. The point is not to make every callback tiny for aesthetic reasons. It is to keep time-sensitive work from inheriting the worst-case duration of unrelated work.

This is why executor choices belong in a robot’s architecture review. The right arrangement depends on the workload, the acceptable latency, and the kinds of failure the system must tolerate. A camera-heavy inspection robot may need a different execution plan from a small mobile platform, even when both use the same ROS 2 APIs.

ROS 2 makes the scheduling layer explicit, which is useful precisely because it refuses to hide the trade-off. Responsiveness comes from the whole path: callback groups, executor threads, work duration, and the way data moves between them. Once those choices are treated as part of the design, a robot’s “feel” becomes something an engineering team can measure and improve.