Thread Synchronization 互動教學

🔒 Mutex 互動體驗:手動控制 Lock/Unlock

互動方式:你可以手動控制兩個執行緒的每個動作!
使用 Mutex:體驗「誰 lock 誰才能 unlock」,其他執行緒必須等待。
不使用 Mutex:兩個執行緒同時操作共享變數,會發生 Race Condition 導致計數錯誤!

🔑 Mutex Lock/Unlock 關鍵重點

  • k_mutex_lock() - 嘗試「取得鎖」,如果已被佔用就無法執行(按鈕會被禁用)
  • 臨界區 (Critical Section) - 只有持有鎖的執行緒才能安全執行 counter++
  • k_mutex_unlock() - 只有「持有者」才能釋放,其他執行緒的 unlock 按鈕會被禁用
  • 擁有權原則 - 誰 lock 就必須誰 unlock(不能由其他執行緒代勞)
🎮 模式選擇:
當前模式:✅ 使用 Mutex 保護
⚠️ Race Condition 發生!兩個執行緒同時讀取相同值,導致其中一次 +1 失效!
執行緒 1
個人計數: 0
狀態: Idle
🔓
Unlocked
無持有者
執行緒 2
個人計數: 0
狀態: Idle
共享計數器 (Shared Counter): 0

程式碼範例

static volatile uint32_t counter = 0; K_MUTEX_DEFINE(counter_lock); void thread_function() { /* 步驟 1: 取得鎖 */ k_mutex_lock(&counter_lock, K_FOREVER); /* 步驟 2: 臨界區 - 只有持有鎖的執行緒可以執行 */ counter++; /* 步驟 3: 釋放鎖(必須由持有者釋放)*/ k_mutex_unlock(&counter_lock); }

📡 Semaphore 示範 A:ISR → 執行緒通知

情境:模擬每秒的 Timer ISR 發出通知,工作執行緒收到後才處理取樣。
重點:k_sem_give() 可在 ISR 呼叫,而 Mutex 不行!
⏰ Timer ISR
等待觸發...
工作執行緒
等待通知...
已處理取樣: 0

程式碼範例

K_SEM_DEFINE(sample_sem, 0, 1); static void timer_handler() { /* ISR context:只做發訊號 */ k_sem_give(&sample_sem); } void worker() { while (1) { k_sem_take(&sample_sem, K_FOREVER); /* 真正工作放在執行緒做 */ process_sampling(); } }

🎯 Semaphore 示範 B:資源池(最多 3 個)

情境:系統同時最多允許 3 個 DMA buffer 被使用。
第 4 個嘗試:會阻塞等待,直到有人釋放資源。
1
2
3
可用資源: 3 / 3

程式碼範例

/* 初始可用 3 個,最大 3 個 */ K_SEM_DEFINE(buf_sem, 3, 3); void use_buffer() { /* 沒名額就等待 */ k_sem_take(&buf_sem, K_FOREVER); /* 使用 buffer ... */ do_work(); /* 用完歸還名額 */ k_sem_give(&buf_sem); }

📊 Semaphore vs Mutex 比較表

項目 Semaphore(訊號量) Mutex(互斥鎖)
本質 一個計數器(可設定最大值) 一把鎖(一次只能一個持有者)
擁有權 無擁有者:A take,B 也能 give 有擁有者:誰 lock 就必須誰 unlock
主要用途 1) 事件/通知
2) 資源池管理
保護臨界區(同時僅一執行緒可進入)
ISR 使用 ✅ k_sem_give() 可在 ISR 呼叫 ❌ 不能在 ISR lock/unlock
優先權反轉 沒有內建處理機制 ✅ Zephyr 具優先權繼承
Race Condition 無法防止(設計目的不同) ✅ 可防止多執行緒同時存取共享資源

🎯 選擇決策

  • 保護共享資料的臨界區 → 用 Mutex
  • ISR/裝置 → 執行緒的喚醒/通知 → 用 Semaphore
  • 固定數量資源池 → 用 Counting Semaphore
  • 千萬別在 ISR 用 mutex;也不要用 semaphore 當互斥鎖