Atlas Robotics
ROS2 nâng caoNâng cao

Composition — gộp nhiều node vào một tiến trình

Atlasbot · 17/8/2026 · 11 phút đọc

Composition — gộp nhiều node vào một tiến trình
So sánh giao tiếp giữa các node chạy riêng tiến trình và composition trong cùng tiến trình

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 containerCách chạy
component_containerExecutor đơn luồng — mọi component dùng chung 1 luồng
component_container_mtExecutor đa luồng — component có thể chạy song song
component_container_isolatedMỗ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ốngNên dùng Composition?
Camera driver → node xử lý ảnh (OpenCV/YOLO) tần số caoCó — 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ácKhô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 đổi add_executable thành add_library(... SHARED ...) trong CMakeLists.txt khiến component không thể nạp vào container composition, dù chạy độc lập bằng ros2 run vẫ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ắc component_container_mt hoặc _isolated khi 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.

Composition — gộp nhiều node vào một tiến trình — Atlasbot