write one ros2 c++ multithread demo, or is there a...
Créé le : 23 mai 2025
Créé le : 23 mai 2025
write one ros2 c++ multithread demo, or is there any offical demos I can test directly?
Below is a ready-to-compile mini C++ demo that exercises ROS 2’s MultiThreadedExecutor
, followed by several official demos you can run out-of-the-box.
ROS 2 ships an official multithreaded executor sample in the ros2/examples repo and multithreaded talker/listener executables in the demo_nodes_cpp package. All use rclcpp::executors::MultiThreadedExecutor
, which spins multiple callbacks in parallel to keep high-frequency timers, subscriptions, and services responsive. The self-contained code below shows the same pattern in a single node; compile it with any recent ROS 2 distro (Humble → Iron).GitHub ROS Documentation ROS Documentation ROS Documentation
src/dual_timer_node.cpp
)cpp#include <chrono> #include <memory> #include <rclcpp/rclcpp.hpp> using namespace std::chrono_literals; class DualTimerNode : public rclcpp::Node { public: DualTimerNode() : Node("dual_timer_node") { // Timer 1: publishes fast timer_fast_ = this->create_wall_timer( 50ms, // 20 Hz [this]() { RCLCPP_INFO(this->get_logger(), "Fast timer thread-id: %zu", std::hash<std::thread::id>{}(std::this_thread::get_id())); }); // Timer 2: publishes slowly and sleeps to simulate heavy work timer_slow_ = this->create_wall_timer( 500ms, // 2 Hz [this]() { RCLCPP_INFO(this->get_logger(), "Slow timer START thread-id: %zu", std::hash<std::thread::id>{}(std::this_thread::get_id())); std::this_thread::sleep_for(300ms); // heavy callback RCLCPP_INFO(this->get_logger(), "Slow timer END"); }); } private: rclcpp::TimerBase::SharedPtr timer_fast_; rclcpp::TimerBase::SharedPtr timer_slow_; }; int main(int argc, char ** argv) { rclcpp::init(argc, argv); auto node = std::make_shared<DualTimerNode>(); // Spin the node with 4 worker threads rclcpp::executors::MultiThreadedExecutor exec(rclcpp::ExecutorOptions(), 4); exec.add_node(node); exec.spin(); rclcpp::shutdown(); return 0; }
bash# Create an ament_cmake package that depends on rclcpp ros2 pkg create --build-type ament_cmake mt_demo --dependencies rclcpp cd mt_demo # Add the source file and edit CMakeLists.txt: echo 'add_executable(dual_timer_node src/dual_timer_node.cpp) ament_target_dependencies(dual_timer_node rclcpp) install(TARGETS dual_timer_node DESTINATION lib/${PROJECT_NAME})' >> CMakeLists.txt # Build colcon build --packages-select mt_demo source install/setup.bash
bashros2 run mt_demo dual_timer_node
You should see the fast timer continue printing even while the slow timer is sleeping, and the logs will show different thread IDs—confirmation that callbacks are executing in parallel.
Package | Executable | What it shows | How to run |
---|---|---|---|
examples/rclcpp/executors/multithreaded_executorGitHub | multithreaded_executor | One publisher and one timer placed in two mutually exclusive callback groups, spun by MultiThreadedExecutor . Demonstrates how parallelism avoids starvation. | ros2 run examples_rclcpp_multithreaded_executor multithreaded_executor |
demo_nodes_cpp (since Foxy)ROS Documentation | talker_multithreaded / listener_multithreaded | Canonical talker/listener pair that uses a multithreaded executor so publishing and subscription callbacks do not block one another. | ros2 run demo_nodes_cpp talker_multithreaded in one shell, ros2 run demo_nodes_cpp listener_multithreaded in another |
mtexec_example (community repo)GitHub | nav_node , base_node | Shows a service and a subscription executing concurrently in separate threads using custom callback groups. | Clone repo, colcon build , then run nodes per README |
Concepts tutorial “About Executors” sectionROS Documentation | N/A | Explains design trade-offs between single-threaded, multi-threaded and static executors, with code snippets in C++. | Read docs |
The Construct multithreaded tutorialThe Construct | Online notebook | Step-by-step exercise that times message processing with and without multithreading. | Follow tutorial in browser |
Tip: All official examples live in your ROS 2 installation under
ros_ws/install/**/lib
for binaries orsrc/ros2/examples
if you cloned the source tree. Useros2 pkg executables <pkgname>
to list what is installed.
0
to the MultiThreadedExecutor
constructor lets it choose std::thread::hardware_concurrency()
threads automatically.ROS Documentationdemo_nodes_cpp_native
. Double-check with ros2 pkg list | grep demo
.docs.ros2.orgDEMO_EXECUTOR
CMake flag (e.g. EventsExecutor).ROS Documentationrqt_graph
to confirm that topics are flowing as expected while threads run in parallel.rclcpp::executors::MultiThreadedExecutor
docs (Humble).ROS Documentationros2 trace
).Enjoy exploring ROS 2 multithreading!
the multi thread executor will use multi thread, how to check the thread number, I have one virtual machine, seems the ros2 run examples_rclcpp_multithreaded_executor multithreaded_executor
runs more threads than cores in vm
Below is a practical “cheat-sheet” that explains how many threads a ROS 2 process really owns, why you often see more threads than CPU cores, and several ways to measure or limit them.
rclcpp::executors::MultiThreadedExecutor
creates a pool whose size defaults to std::thread::hardware_concurrency()
with a minimum of two threads when you pass 0
or omit the argument ROS Documentation ROS Documentation.
Your ROS 2 process, however, also inherits extra middleware threads (Fast DDS, Cyclone DDS, Connext, etc.), logging threads, transport threads, flow-controller threads, and sometimes OS helper threads, so the total thread count you observe in a tool like htop -H
can be two-to-three times the number of virtual CPUs fast-rtps.docs.eprosima.com Fast DDS Documentation cyclonedds.io fast-rtps.docs.eprosima.com.
You can check the live count with simple Linux commands, print it from inside your node, or explicitly cap it when constructing the executor.
Constructor call | Thread-pool size |
---|---|
MultiThreadedExecutor() or MultiThreadedExecutor(options, 0) | hardware_concurrency() (min 2) |
MultiThreadedExecutor(options, N) | Exactly N |
The behaviour is documented in the C++ API for every ROS 2 distro—from Dashing to Rolling ROS Documentation ROS Documentation and originated from the design discussion in rclcpp issue #441 GitHub.
Even if you set the pool to two, your process still hosts:
cyclonedds.xml
under <Threads>
cyclonedds.ioHence it is perfectly normal to see, say, 20–25 LWPs for a four-core VM running the stock “multithreaded executor” demo.
Tool / command | What it shows |
---|---|
htop -H -p <PID> | live list of LWPs with CPU usage |
ps -L -p <PID> or ps -o nlwp <PID> | raw count via the NLWP column Ask Ubuntu |
grep Threads /proc/<PID>/status | single number, good for scripts |
For a one-liner:
bashPID=$(pgrep multithreaded_executor) echo "Threads: $(grep Threads /proc/$PID/status)"
Stack Overflow and Ubuntu man-pages confirm the nlwp
trick Stack Overflow Baeldung.
cpp#include <thread> RCLCPP_INFO(get_logger(), "pool has %zu worker threads (hardware reports %u)", executor.get_number_of_threads(), // C++23 rclcpp exposes this std::thread::hardware_concurrency());
If you are on Humble or older, there is no accessor; keep a copy of the size you passed to the constructor or read hardware_concurrency()
directly.
The official multithreaded-executor demo prints this number on start-up (look for “Created multithreaded executor. Num threads: …” in its source) GitHub.
cpp// Limit the executor to two worker threads rclcpp::executors::MultiThreadedExecutor exec(rclcpp::ExecutorOptions(), 2);
Passing 2
here keeps timer/subscription callbacks concurrent yet ensures the executor itself never spawns more than two workers, regardless of CPU count or hyper-threading ROS Documentation.
The example multithreaded_executor
accepts a --num-threads
command-line flag (added in 2023) so you can test different pool sizes without recompiling:
bashros2 run examples_rclcpp_multithreaded_executor multithreaded_executor --ros-args -p num_threads:=2
(If your installation predates that patch, edit the constructor call and rebuild.)
<Thread name="...">
entries where you can set enabled="false"
or lower priorities cyclonedds.io.FASTRTPS_DEFAULT_PROFILES_FILE
or CYCLONEDDS_URI
is set.Be aware that disabling discovery or transport threads can break communication; test carefully.
hardware_concurrency()
returns virtual CPUs; check lscpu
to see what the hypervisor has exposed.pthread_setaffinity_np
) and keep DDS background threads on other cores GitHub.Do you… | Then… |
---|---|
Only want to measure | Use ps -o nlwp <PID> or htop -H . |
Need to limit executor threads | Pass the desired count in the constructor. |
Need to limit middleware threads | Provide an XML profile to your RMW layer. |
Debug excessive threads | Count again after starting with FASTRTPS_DEFAULT_PROFILES_FILE that disables SHM and logging threads. |
With these techniques you can reconcile the thread counts you see in your VM with what ROS 2 actually needs, and keep resource usage predictable.
write one C++ demo code to print std::thread::hardware_concurrency()
directly, buit it and run
Certainly! Here's a simple C++ program that prints the number of concurrent threads supported by your system using
std::thread::hardware_concurrency()
:
cpp#include <iostream> #include <thread> int main() { unsigned int n = std::thread::hardware_concurrency(); if (n == 0) { std::cout << "Unable to determine the number of concurrent threads supported.\n"; } else { std::cout << n << " concurrent threads are supported.\n"; } return 0; }
If you're using GCC or Clang, compile the program with the following command:
bashg++ -std=c++11 -pthread -o hw_threads hw_threads.cpp
Explanation of flags:
-std=c++11
: Enables C++11 features, which include std::thread
.-pthread
: Links the POSIX thread library, necessary for multithreading support.After successful compilation, run the program:
bash./hw_threads
You should see output similar to:
8 concurrent threads are supported.
(Note: The actual number may vary depending on your system's hardware.)
The function std::thread::hardware_concurrency()
returns the number of concurrent threads supported by the implementation. This value is only a hint and may not be accurate. If the value is not well defined or not computable, the function returns 0
.
The number returned typically corresponds to the number of hardware threads (logical cores) available to the program. This includes considerations like hyper-threading and may be affected by system configurations or restrictions.
If you have any further questions or need assistance with multithreading in C++, feel free to ask!
how to local build and run ros2 demo codes, like in: https://github.com/ros2/examples/blob/rolling/rclcpp/executors/multithreaded_executor/CMakeLists.txt
To build and run the ROS 2 C++ demo from the multithreaded_executor example, follow these steps:
Assuming you have ROS 2 (e.g., Rolling, Humble, or Iron) installed:
bashmkdir -p ~/ros2_ws/src cd ~/ros2_ws/src
examples
Repositorybashgit clone https://github.com/ros2/examples.git
This will clone the examples
repository into your workspace's src
directory.
Navigate to the root of your workspace and build using colcon
:
bashcd ~/ros2_ws source /opt/ros/<ros_distro>/setup.bash # Replace <ros_distro> with your ROS 2 distribution name colcon build --packages-select examples_rclcpp_multithreaded_executor
Replace <ros_distro>
with your installed ROS 2 distribution (e.g., rolling
, humble
, or iron
).
After building, source the workspace and run the demo:
bashsource install/setup.bash ros2 run examples_rclcpp_multithreaded_executor multithreaded_executor
You should see output indicating that the multithreaded executor is running, along with messages from the publisher and subscriber nodes.
To specify the number of threads used by the MultiThreadedExecutor
, you can modify the source code. Open the multithreaded_executor.cpp
file:
bashnano ~/ros2_ws/src/examples/rclcpp/executors/multithreaded_executor/multithreaded_executor.cpp
Locate the line where the executor is created:
cpprclcpp::executors::MultiThreadedExecutor executor;
Modify it to specify the desired number of threads (e.g., 4):
cpprclcpp::executors::MultiThreadedExecutor executor(rclcpp::ExecutorOptions(), 4);
Save the file and rebuild the package:
bashcd ~/ros2_ws colcon build --packages-select examples_rclcpp_multithreaded_executor
Then, source the workspace and run the demo again:
bashsource install/setup.bash ros2 run examples_rclcpp_multithreaded_executor multithreaded_executor
This will run the demo using the specified number of threads.
examples
repository contains various other examples demonstrating different ROS 2 features. Feel free to explore them to deepen your understanding.If you encounter any issues or have further questions, feel free to ask!
write one minimal codes, to test ros2 multithread executor
Certainly! Here's a minimal C++ example demonstrating the use of ROS 2's
MultiThreadedExecutor
. This example sets up a node with two timers: one fast and one slow. The slow timer simulates a long-running task by sleeping for a short duration. Using the MultiThreadedExecutor
, both timers can operate concurrently without blocking each other.
File: src/multithreaded_timer_node.cpp
cpp#include <chrono> #include <memory> #include <thread> #include <rclcpp/rclcpp.hpp> using namespace std::chrono_literals; class MultiThreadedTimerNode : public rclcpp::Node { public: MultiThreadedTimerNode() : Node("multi_threaded_timer_node") { // Fast timer: triggers every 100ms fast_timer_ = this->create_wall_timer( 100ms, [this]() { RCLCPP_INFO(this->get_logger(), "Fast timer callback"); }); // Slow timer: triggers every 500ms and sleeps for 300ms slow_timer_ = this->create_wall_timer( 500ms, [this]() { RCLCPP_INFO(this->get_logger(), "Slow timer callback start"); std::this_thread::sleep_for(300ms); RCLCPP_INFO(this->get_logger(), "Slow timer callback end"); }); } private: rclcpp::TimerBase::SharedPtr fast_timer_; rclcpp::TimerBase::SharedPtr slow_timer_; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); auto node = std::make_shared<MultiThreadedTimerNode>(); // Create a MultiThreadedExecutor with 2 threads rclcpp::executors::MultiThreadedExecutor executor(rclcpp::ExecutorOptions(), 2); executor.add_node(node); executor.spin(); rclcpp::shutdown(); return 0; }
Create a new ROS 2 package:
bashros2 pkg create --build-type ament_cmake multithreaded_executor_demo --dependencies rclcpp
Add the source file:
Place the multithreaded_timer_node.cpp
file into the src
directory of your package.
Modify CMakeLists.txt
:
Add the following lines to your CMakeLists.txt
to build the executable:
cmakeadd_executable(multithreaded_timer_node src/multithreaded_timer_node.cpp) ament_target_dependencies(multithreaded_timer_node rclcpp) install(TARGETS multithreaded_timer_node DESTINATION lib/${PROJECT_NAME})
Build the package:
bashcd ~/ros2_ws colcon build --packages-select multithreaded_executor_demo
Source the workspace and run the node:
bashsource install/setup.bash ros2 run multithreaded_executor_demo multithreaded_timer_node
With the MultiThreadedExecutor
, both timers operate concurrently:
Because of the multithreaded execution, the fast timer continues to operate without being blocked by the slow timer's sleep duration.
For more detailed examples and explanations, you can refer to the official ROS 2 examples repository:
This example demonstrates the use of callback groups and multiple nodes with the MultiThreadedExecutor
.
Feel free to explore and modify these examples to better understand multithreaded execution in ROS 2!
Why the codes doing noting will hold 9 threads? even with SingleThreadedExecutor
text#include <chrono> #include <memory> #include <rclcpp/rclcpp.hpp> using namespace std::chrono_literals; class DummpyNode : public rclcpp::Node { public: DummpyNode() : Node("dummpy_node") { } }; int main(int argc, char ** argv) { rclcpp::init(argc, argv); auto node = std::make_shared<DummpyNode>(); //rclcpp::executors::MultiThreadedExecutor exec(rclcpp::ExecutorOptions(), 4); //rclcpp::executors::MultiThreadedExecutor exec(rclcpp::ExecutorOptions(), 2); rclcpp::executors::SingleThreadedExecutor exec; exec.add_node(node); exec.spin(); rclcpp::shutdown(); return 0; }
When you launch even the simplest ROS 2 C++ program, the executor thread you create is only a small part of the story.
Most of the 8-10 extra lightweight-processes (threads) you see in htop
or ps -L
are started by the underlying DDS middleware (Fast DDS by default, Cyclone DDS, Connext, etc.), plus a few helper threads from rclcpp and the C++ standard library. In other words, the SingleThreadedExecutor really is single-threaded—it just shares its process with many other components that need their own threads to keep the network, timers and logging working.
SingleThreadedExecutor
runs callbacks in the same thread that calls spin()
; it creates no worker pool of its own. The multi-threaded version only enlarges that pool. ROS DocumentationFast DDS starts several dedicated threads per DomainParticipant, for example Fast DDS Documentation:
Purpose (Fast DDS) | Typical count | Notes |
---|---|---|
Transport reception (UDP + SHM) | ≥ 2 | One per builtin transport |
Timed-events / heartbeat | 1 | Drives reliability timers |
Discovery & Type-Lookup | 1–2 | Finds peers, exchanges type info |
Logging consumer | 1 | Feeds the async log queue Fast DDS Documentation |
These already add 6-7 threads before your code runs.
Cyclone DDS creates similar helper threads—each individually tunable in <Threads>
XML stanzas such as receiver_thread
, event_thread
, watchdog_thread
, etc. cyclonedds.io
std::async
) inside third-party dependencies may also spawn background workers.On a typical desktop Fast DDS configuration:
text1 executor/spin loop 1 DDS timed-events 2 UDP receive workers 2 shared-memory receive workers 1 participant discovery 1 Type-Lookup 1 Fast DDS async logger ------------------------- 9 total threads
Community reports match this order of magnitude, even for nodes that “do nothing” ROS Discourse Robotics Stack Exchange.
bashPID=$(pgrep dummpy_node) # your process name here ps -o nlwp,cmd -p $PID # NLWP column = #threads htop -H -p $PID # live per-thread view grep Threads /proc/$PID/status
These methods are the standard Linux way to verify what ROS 2 launches under the hood. The Construct
Yes—mostly by configuring the DDS layer, because that is where the surplus threads originate.
SYNCHRONOUS_PUBLISH_MODE
, which removes the background writer thread. Fast DDS Documentation Fast DDS Documentation<ThreadSettings>
block (priority, stack, enabled flag) in the XML schema Fast DDS Documentation.CYCLONEDDS_URI=<file>.xml
and set enabled="false"
for unneeded threads inside <Threads>
. cyclonedds.io cyclonedds.ioLimiting the executor pool (e.g., MultiThreadedExecutor(..., 2)
) helps only when you switch to the multi-threaded executor; it does not affect the middleware threads.
Removing DDS threads may save memory or avoid oversubscription on small VMs, but it usually comes at the cost of lower throughput or slower discovery. Measure before and after, especially on embedded targets ROS Discourse.
ps
, htop -H
, or /proc/<pid>/status
to confirm changes.With this picture in mind, the extra threads you observe are expected and configurable, not a malfunction of SingleThreadedExecutor
.