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:
- Cấu trúc: state machine cho ra đúng một
velocity_sourcemỗi cycle, vàrun_controllervớitick_recoverykhông bao giờ cùng bật. - 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 trongControlLoop::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. PlannerFeedbackkhô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.