From f7d4365914487ff80b10d2684c10ad2ed8932c53 Mon Sep 17 00:00:00 2001 From: ken910606 Date: Wed, 1 Apr 2026 14:41:18 +0800 Subject: [PATCH] Delete 'src/GUI/THREAD_SAFETY.md' --- src/GUI/THREAD_SAFETY.md | 218 --------------------------------------- 1 file changed, 218 deletions(-) delete mode 100644 src/GUI/THREAD_SAFETY.md diff --git a/src/GUI/THREAD_SAFETY.md b/src/GUI/THREAD_SAFETY.md deleted file mode 100644 index 0b68514..0000000 --- a/src/GUI/THREAD_SAFETY.md +++ /dev/null @@ -1,218 +0,0 @@ -# 執行緒安全性實現 - GCS GUI - -## 架構概述 - -GCS GUI 採用 **拉取式 UI 更新架構** 以確保執行緒安全和高效的UI渲染,避免UI執行緒被資料收集堵塞。 - -## 執行緒模型 - -### 1. **主 GUI 執行緒** (Qt Event Loop) -- 負責所有 UI 操作(更新標籤、表格、地圖、ADI 等) -- 執行 `_process_ui_updates()` 每 33ms(30Hz) - -### 2. **ROS 執行緒** (Single-threaded Executor) -- 執行 `spin_ros()` 定時器每 10ms -- 只負責收集資料並快取,**不進行任何 UI 操作** - -### 3. **背景接收執行緒** (Receiver Threads) -- UDP/WebSocket/Serial 接收器在獨立執行緒上運行 -- 寫入 `monitor.latest_data` 字典(快取層) - -### 4. **任務執行執行緒** (Mission Executor) -- 運行在獨立執行緒上 -- 不直接訪問 UI 元素 - -## 資料流 - -``` -接收執行緒 - ↓ -monitor.latest_data (共享字典) - ↓ -spin_ros() [UI 執行緒, 10ms] - ↓ (快取資料,不更新 UI) -_ui_update_cache (字典) - ↓ -_process_ui_updates() [UI 執行緒, 30Hz] - ↓ (批次處理,更新 UI) -DronePanel, OverviewTable, DroneMap -``` - -## 執行緒安全機制 - -### 1. **_ui_cache_lock (布林鎖)** -```python -# 在 __init__ 中初始化 -self._ui_cache_lock = False -self._ui_update_cache = {} - -# 在 spin_ros() 中快取資料 -self._ui_update_cache[drone_id][msg_type] = data - -# 在 _process_ui_updates() 中保護快取訪問 -if self._ui_cache_lock or not self._ui_update_cache: - return -self._ui_cache_lock = True -try: - # 處理快取資料 - for drone_id in list(self._ui_update_cache.keys()): - ... -finally: - self._ui_cache_lock = False -``` - -### 2. **drone_positions 與 drone_headings 訪問** -- **寫入**: 在 `_update_gps_ui()` 和 `_update_hud_ui()` 中進行 - - 這些方法**只在 UI 執行緒**的 `_process_ui_updates()` 中調用 - -- **讀取**: 在 `_update_attitude_ui()` 和 `_update_hud_ui()` 中讀取 - - 這些方法**只在 UI 執行緒**的 `_process_ui_updates()` 中調用 - -```python -# 只在 UI 執行緒上讀寫 -heading = self.drone_headings.get(drone_id, 0) # 讀取 -self.drone_headings[drone_id] = heading # 寫入 -``` - -### 3. **資料同步屬性** - -| 屬性 | 寫入執行緒 | 讀取執行緒 | 保護機制 | -|------|----------|----------|--------| -| `_ui_update_cache` | spin_ros (UI) | _process_ui_updates (UI) | _ui_cache_lock | -| `monitor.latest_data` | 接收執行緒 | spin_ros (UI) | 直接讀取後清空 | -| `drone_positions` | _update_gps_ui (UI) | _update_hud_ui (UI) | 同一執行緒 | -| `drone_headings` | _update_hud_ui (UI) | _update_attitude_ui (UI) | 同一執行緒 | -| `self.drones[*]` | add_drone (UI) | 各 _update_*_ui (UI) | 同一執行緒 | - -## UI 更新流程 - -### 步驟 1: 資料收集 (spin_ros, 10ms) -```python -def spin_ros(self): - # 執行 ROS 收集資料 - self.executor.spin_once(timeout_sec=0.01) - - # 只快取資料,不更新 UI - for (drone_id, msg_type), data in self.monitor.latest_data.items(): - if drone_id not in self._ui_update_cache: - self._ui_update_cache[drone_id] = {} - self._ui_update_cache[drone_id][msg_type] = data - self.monitor.latest_data.clear() -``` - -### 步驟 2: 批次 UI 更新 (_process_ui_updates, 30Hz / 33ms) -```python -def _process_ui_updates(self): - # 檢查是否有資料且無鎖定 - if self._ui_cache_lock or not self._ui_update_cache: - return - - self._ui_cache_lock = True # 上鎖 - try: - # 批次處理各無人機的快取資料 - for drone_id in list(self._ui_update_cache.keys()): - data_dict = self._ui_update_cache.get(drone_id, {}) - - # 依訊息類型調用相應的 UI 更新方法 - if 'attitude' in data_dict: - self._update_attitude_ui(drone_id, data_dict['attitude']) - if 'hud' in data_dict: - self._update_hud_ui(drone_id, data_dict['hud']) - # ... 其他訊息類型 - - # 清空快取 - self._ui_update_cache.clear() - finally: - self._ui_cache_lock = False # 解鎖 -``` - -### 步驟 3: 各資料類型的 UI 更新 -```python -# 所有 _update_*_ui 方法都在 UI 執行緒上運行 -def _update_gps_ui(self, drone_id, data): - # 安全地寫入共享結構(同一執行緒) - self.drone_positions[drone_id] = (lat, lon) - - # 更新 UI 元素 - self.update_overview_table(drone_id, 'latitude', ...) - self.drone_map.update_drone_position(drone_id, lat, lon, heading) - -def _update_hud_ui(self, drone_id, data): - # 安全地寫入共享結構(同一執行緒) - self.drone_headings[drone_id] = heading - - # 讀取之前寫入的資料(同一執行緒,無競賽條件) - if drone_id in self.drone_positions: - lat, lon = self.drone_positions[drone_id] - self.drone_map.update_drone_position(drone_id, lat, lon, heading) -``` - -## 關鍵設計原則 - -### 1. **執行緒隔離** -- 所有 UI 操作都在主 GUI 執行緒上進行 -- ROS 資料收集隔離在單獨的定時器回調中 -- 背景執行緒只寫入快取,不觸及 UI - -### 2. **資料流向一致** -``` -背景執行緒 → monitor.latest_data - ↓ - 主 UI 執行緒 (spin_ros) - ↓ - _ui_update_cache - ↓ - 主 UI 執行緒 (_process_ui_updates) - ↓ - UI 元素 -``` - -### 3. **批次更新減少開銷** -- 不是每次收到訊息就更新 UI(造成主執行緒壓力) -- 而是批次收集 33ms 內的所有更新,一次性渲染 -- 結果:UI 流暢度提高,執行緒爭奪減少 - -### 4. **簡單的同步策略** -- 使用簡單的布林標誌而不是複雜的鎖 -- 避免死鎖,因為只有一個重要的臨界區 (`_ui_update_cache`) - -## 監控與驗證 - -### 態度指示器(ADI)更新頻率 -在 [drone_panel.py](drone_panel.py) 中的 `update_attitude()` 方法自動列印頻率: -``` -[drone_0] Attitude update frequency: 30.00 Hz -[drone_1] Attitude update frequency: 29.98 Hz -``` - -### 預期頻率 -- **資料收集**: 10ms = 100Hz(但不更新 UI) -- **UI 渲染**: 30Hz(批次模式) -- **ADI 更新**: 約 30Hz(受限於 `_process_ui_updates()` 頻率) - -## 故障排除 - -### 1. ADI 仍然更新頻率低 -- 檢查 `ui_update_timer` 是否啟動 -- 確認 `_process_ui_updates()` 正在被調用 -- 檢查是否有其他執行緒在嘗試直接更新 UI - -### 2. 地圖或表格更新時卡頓 -- 檢查 `drone_map.update_drone_position()` 和 `update_overview_table()` 是否有阻塞操作 -- 確認這些操作在 `_process_ui_updates()` 中被調用(主執行緒) - -### 3. 資料不同步 -- 驗證 `_ui_cache_lock` 是否正確保護快取訪問 -- 檢查是否有代碼在 spin_ros 之外寫入 `_ui_update_cache` - -## 測試建議 - -1. **頻率監控**: 執行應用程序並查看列印的 ADI 更新頻率 -2. **視覺檢查**: 觀察 ADI 指標的平滑性和應答性 -3. **壓力測試**: 同時連接多個無人機並監控 CPU 使用率 -4. **QDebug**: 在時間敏感的部分添加 `print()` 以測量執行時間 - ---- - -**更新日期**: 2025-01-20 -**版本**: 執行緒安全優化 v1.0