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

73 lines
4.2 KiB
Markdown

# 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** |
`ControlLoop``StateMachine` **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.