73 lines
4.2 KiB
Markdown
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` và `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()` là **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.
|