Files
move_base2/docs/THREADING.md
2026-07-29 15:45:16 +07:00

4.2 KiB

Mô hình thread và quyền sở hữu

Bảng thread

Thread Sở hữu Chu kỳ Ghi chú
control state machine, bộ trọng tài vận tốc, controller, recovery, cmd_vel controller_frequency Thread duy nhất được phát cmd_vel. Toàn bộ ControlLoop::step() chạy ở đây
planner global planner, bộ đệm plan planner_frequency hoặc theo biến điều kiện Chưa có ở trạng thái hiện tại — xem mục dưới
mission_event hàng đợi sự kiện mission theo sự kiện Thuộc mission framework, không thuộc gói này
mission_exec điều phối chặng mission theo sự kiện Thuộc mission framework. Chỉ gọi callback, không được block
host (ROS / C#) nhận dữ liệu sensor, gọi API BaseNavigation tuỳ host Ghi vào vùng dữ liệu sensor qua mutex

Bất biến quan trọng nhất

Chỉ thread control được phát cmd_vel.

Đây là lý do recovery được tick từ control thread thay vì chạy trong thread riêng. Recovery thế hệ 2 có thể phát vận tốc; nếu nó chạy ở thread riêng thì sẽ có hai bộ điều khiển cùng ghi vào một đường lệnh, và không có cách nào xác định được lệnh nào tới trước. Đó là hai bộ điều khiển tranh nhau, không phải hai tác vụ song song.

Bất biến này được củng cố ở hai chỗ, độc lập nhau:

  1. Cấu trúc: state machine cho ra đúng một velocity_source mỗi cycle, và run_controller với tick_recovery không bao giờ cùng bật.
  2. Cổng ra: mọi lệnh đều đi qua VelocityArbiter, và bộ trọng tài chỉ được gọi một lần trong ControlLoop::step().

Mutex

Mutex Bảo vệ Ai giữ
NavigationServer::data_mutex_ bản đồ tĩnh, laser scan, point cloud, depth camera, odometry, footprint host khi ghi, control thread khi đọc
mutex của costmap dữ liệu costmap phần nối dây; lấy lại mỗi lần dùng, không cache

ControlLoopStateMachine không có mutex nào và cố ý như vậy: chúng chỉ chạy trên control thread. Thêm mutex vào đó sẽ che mất việc có ai đó gọi sai thread.

Con trỏ costmap

Con trỏ lấy từ getCostmap()non-owning và có thể bị thay giữa hai cycle. Cache lại chính là nguyên nhân lỗi double-free đã ghi nhận trong workspace. Quy tắc áp cho mọi hiện thực port:

  • Lấy lại con trỏ ở đầu mỗi lần dùng.
  • Không giữ tham chiếu qua nhiều cycle.
  • Không giữ tham chiếu qua ranh giới thread.

Trạng thái hiện tại: một thread

ControlLoop hiện chạy đồng bộ, một thread, và lập plan ngay trong step(). Đây là lựa chọn có chủ đích cho bước dựng khung: phần quyết định kiểm được đầy đủ mà không cần thread nào, nên test chạy tất định và không có race.

Khi thêm thread planner ở bước sau:

  • Giữ nguyên mô hình bộ đệm plan ba lớp của bản cũ. Nó đang hoạt động tốt và là đoạn code tinh tế — bê nguyên si trước, không "cải tiến" cùng lúc với việc chuyển sang kiến trúc mới.
  • Điểm nối là ControlLoop::runPlanner(): thay lời gọi đồng bộ bằng việc đánh thức thread planner và đọc kết quả từ bộ đệm.
  • PlannerFeedback không đổi. Đó chính là mục đích của việc tách nó thành một enum: state machine không cần biết plan được tính đồng bộ hay bất đồng bộ.

Vòng đời

Đối tượng Ai sở hữu
NavigationServer host, qua shared_ptr do factory plugin trả về
ControlLoop, StateMachine, VelocityArbiter NavigationServer, theo giá trị
Mọi cổng (ControlLoopDeps) không sở hữu; phải sống lâu hơn ControlLoop

ControlLoopDeps chỉ chứa con trỏ trần và điều này là cố ý: nó nói rõ rằng control loop không sở hữu gì cả. Bên nối dây chịu trách nhiệm giữ các cổng sống đủ lâu.