Composition — gộp nhiều node vào một tiến trình
Atlasbot · 17/8/2026 · 11 phút đọc
Mặc định, mỗi lần ros2 run là một tiến trình hệ điều hành riêng — hai node trao đổi dữ liệu qua nhau vẫn phải đi qua toàn bộ đường DDS: serialize message, gửi qua socket/loopback, deserialize lại. Với message nhỏ (Twist, Odometry) chi phí này không đáng kể, nhưng với ảnh camera độ phân giải cao publish ở tần số cao, việc serialize/copy liên tục trở thành nút thắt cổ chai thực sự. Composition cho phép gộp nhiều node vào chung một tiến trình, giao tiếp bằng con trỏ bộ nhớ thay vì message qua DDS.
Khái niệm chính
Khác biệt so với node thường: đăng ký node như một plugin qua class_loader, cho phép cùng một class chạy độc lập (ros2 run) hoặc load chung tiến trình (composition) mà không cần sửa code:
// motor_component.cpp
#include "rclcpp_components/register_node_macro.hpp"
class MotorComponent : public rclcpp::Node {
public:
explicit MotorComponent(const rclcpp::NodeOptions & options)
: Node("motor_component", options) { /* ... */ }
};
RCLCPP_COMPONENTS_REGISTER_NODE(MotorComponent)
# CMakeLists.txt
add_library(motor_component SHARED src/motor_component.cpp)
ament_target_dependencies(motor_component rclcpp rclcpp_components)
rclcpp_components_register_nodes(motor_component "MotorComponent")
Điểm khác biệt mấu chốt: build ra thư viện chia sẻ (.so), không phải executable độc lập — một component có thể được nạp vào bất kỳ tiến trình composition nào tại thời điểm chạy.
Nguyên lý hoạt động
Hai cách load component:
# Cách 1: gộp sẵn nhiều component vào 1 tiến trình từ launch file
ros2 launch my_pkg composed_bringup.launch.py
# Cách 2: load động vào 1 container đang chạy sẵn
ros2 run rclcpp_components component_container &
ros2 component load /ComponentManager my_pkg MotorComponent
ros2 component list
component_container là một tiến trình "vỏ rỗng" chờ sẵn, ros2 component load nạp thêm component vào lúc runtime — hữu ích khi muốn thêm/bớt node đang chạy mà không restart cả hệ thống.
Tóm lại: Composition không thay đổi logic bên trong node — nó thay đổi nơi node chạy và cách dữ liệu di chuyển giữa các node. Lợi ích lớn nhất là intra-process communication: publisher và subscriber trong cùng tiến trình có thể chuyển message bằng con trỏ, bỏ qua toàn bộ bước serialize.
Kiến thức kỹ thuật
Công thức
Chi phí tiết kiệm được ước lượng qua việc loại bỏ bước serialize + copy dữ liệu giữa 2 tiến trình:
Chi phí giao tiếp liên tiến trình ≈ thời gian serialize + thời gian copy qua socket + thời gian deserialize
Chi phí giao tiếp intra-process ≈ chỉ truyền 1 con trỏ (gần như 0)
VD: ảnh camera 1920×1080 RGB ≈ 6MB mỗi khung hình
Serialize + copy qua loopback ước lượng có thể tốn vài ms mỗi khung ở tần số cao
→ với camera 30fps, tiết kiệm vài chục ms/giây có ý nghĩa thực sự cho pipeline xử lý ảnh thời gian thực
Thông số
Các loại container khác nhau ảnh hưởng tới cách các component bên trong được lập lịch chạy:
| Loại container | Cách chạy |
|---|---|
component_container | Executor đơn luồng — mọi component dùng chung 1 luồng |
component_container_mt | Executor đa luồng — component có thể chạy song song |
component_container_isolated | Mỗi component có executor/luồng riêng biệt hoàn toàn |
Ứng dụng thực tế
Bảng quyết định khi nào đáng dùng Composition:
| Tình huống | Nên dùng Composition? |
|---|---|
| Camera driver → node xử lý ảnh (OpenCV/YOLO) tần số cao | Có — tiết kiệm serialize ảnh lớn liên tục |
| Vài node điều khiển tốc độ thấp (Twist, trạng thái) | Không cần thiết — chi phí DDS với message nhỏ không đáng kể |
| Cần cách ly lỗi — 1 node crash không kéo sập node khác | Không — cùng tiến trình nghĩa là 1 node crash sập cả nhóm |
Đây là đánh đổi cốt lõi cần cân nhắc theo từng cặp node cụ thể: pipeline camera→AI trên AMR hưởng lợi rõ rệt từ composition, nhưng node giám sát an toàn (emergency stop) nên cố tình chạy tiến trình riêng biệt để đảm bảo 1 node khác crash không kéo theo mất luôn khả năng phản ứng an toàn.
Ví dụ thực tế
$ ros2 component list
Output:
/ComponentManager
1 /camera_driver
2 /image_processor
3 /object_detector
Cả 3 component này chạy chung 1 tiến trình /ComponentManager — dữ liệu ảnh truyền từ camera_driver qua image_processor tới object_detector không cần serialize qua DDS ở từng bước, miễn cả 3 đều publish/subscribe đúng cấu hình intra-process.
Lỗi thường gặp
- Build ra executable thay vì thư viện chia sẻ (
.so) — quên đổiadd_executablethànhadd_library(... SHARED ...)trongCMakeLists.txtkhiến component không thể nạp vào container composition, dù chạy độc lập bằngros2 runvẫn bình thường. - 1 component crash kéo sập toàn bộ container — như đã nói ở bảng quyết định, đây là đánh đổi cố hữu của composition — không nên gộp node cần cách ly lỗi nghiêm ngặt (VD: an toàn) vào chung container với các node khác.
- Thiếu macro đăng ký
RCLCPP_COMPONENTS_REGISTER_NODE— component container không tìm thấy class khi load, báo lỗi "class not found" dù thư viện.sođã build đúng và tồn tại. - Nhầm lẫn container đơn luồng với đa luồng khi có component chạy tác vụ nặng — dùng
component_container(đơn luồng) cho component có callback xử lý lâu (VD: inference AI) có thể chặn luôn các component khác trong cùng container — cân nhắccomponent_container_mthoặc_isolatedkhi có component chạy nặng.
Kết luận
Composition cho phép cùng 1 code component chạy độc lập hoặc gộp chung tiến trình để loại bỏ chi phí serialize/DDS cho dữ liệu lớn tần suất cao — lợi ích rõ rệt nhất với pipeline xử lý ảnh/point cloud, đánh đổi bằng mất khả năng cách ly lỗi giữa các node cùng container. Cân nhắc kỹ theo từng cặp node cụ thể, không áp dụng composition cho mọi node một cách mặc định. Bài tiếp theo trong chuyên mục nói về Callback Group — công cụ giải quyết vấn đề deadlock khi gọi service lồng nhau đã nhắc tới ở chuyên mục ROS2 Communication.