# Thread Synchronization(執行緒同步)
多執行緒同時跑時,如果**同時動到同一份資源**(變數、資料結構、裝置暫存器…),就可能互相踩到,造成**競態條件(race condition)**。
要避免這種「同一時間,不只一人進入關鍵區(critical section)」的情況,就需要**同步機制**來「一次只讓一人進去」。
常見解法:**Semaphore(訊號量)**與**Mutex(互斥鎖)**。
為幫助讀者更加理解相關知識,以下網址為**Semaphore**與**Mutex**的互動式介面
<iframe src="https://catdogbird3.github.io/Thread-Synchronization-Interactive-Lab/" frameborder="1" width="700" height="1000">
Thread Synchronization 互動教學
</iframe>
---
## Semaphore vs Mutex(差異一眼看懂)
| 項目 | Semaphore(訊號量) | Mutex(互斥鎖) |
| ------- | ---------------------------------------------------------------- | ---------------------------------------------------- |
| 本質 | 一個**計數器**(初始化時設定最大值 `max_count`) | 一把**鎖**(一次只能一個持有者) |
| 擁有權 | **無擁有者**:A `take`,B 也能 `give` | **有擁有者**:誰 `lock` 就必須誰 `unlock` |
| 用途 | 1) **事件/通知**(producer 給、consumer 拿) 2) **資源池**(例如 N 個 buffer 可用) | **保護臨界區**(同一時間僅一執行緒能改資料) |
| ISR 使用 | `k_sem_give()` 可在 ISR 呼叫(非常適合中斷→執行緒通知) | **不能**在 ISR lock/unlock |
| 優先權反轉處理 | 沒有內建優先權繼承 | **Zephyr 的 `k_mutex` 具優先權繼承**(緩解 priority inversion) |
| 常見誤用 | 拿來當互斥鎖用,容易寫出難維護的程式 | 用來做事件通知(不合適) |
> 簡單記:
> **要保護一段「只允許同時一人進去」的程式:用 Mutex。**
> **要傳遞事件或表示「有幾個資源可用」:用 Semaphore。**
> 
> 圖一、Semaphore 的示意圖
> 
> 圖二、Mutex 的示意圖
---
## 1) Mutex 範例:保護共享變數,消除競態
**情境**:兩個執行緒各加 10,000 次到同一個全域計數器,沒保護時常會跑出錯的總數。
```c
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
static volatile uint32_t counter = 0;
K_MUTEX_DEFINE(counter_lock);
void adder(void *a, void *b, void *c)
{
for (int i = 0; i < 10000; ++i) {
k_mutex_lock(&counter_lock, K_FOREVER);
/* 臨界區:一次只允許一個 thread 進來修改 counter */
counter++;
k_mutex_unlock(&counter_lock);
}
printk("%s done\n", k_thread_name_get(k_current_get()));
}
K_THREAD_DEFINE(t1, 1024, adder, NULL, NULL, NULL, 2, 0, 0);
K_THREAD_DEFINE(t2, 1024, adder, NULL, NULL, NULL, 2, 0, 0);
void main(void)
{
k_thread_name_set(t1, "T1");
k_thread_name_set(t2, "T2");
k_msleep(2000);
printk("counter=%u (expect 20000)\n", counter);
}
```
*重點*:
* 用 `k_mutex_lock()/unlock()` 把「只允許同時一人」的區段包起來。
* Zephyr 的 `k_mutex` 具**優先權繼承**,高優先緒在鎖等待時,持鎖的低優先緒會暫時「提升」以快點釋放鎖,減少延遲。
---
## 2) Semaphore 範例 A:ISR → Thread 的事件通知
**情境**:Timer/ISR 每 10ms 來一次,告訴工作執行緒「該取樣了」。ISR 不做重活,只發通知。
```c
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
K_SEM_DEFINE(sample_sem, 0, 1);
static void timer_handler(struct k_timer *t)
{
/* ISR/Timer context:只做「發訊號」這種極短動作 */
k_sem_give(&sample_sem);
}
K_TIMER_DEFINE(tmr, timer_handler, NULL);
void worker(void *a, void *b, void *c)
{
while (1) {
/* 沒通知就睡,等 ISR 喚醒 */
k_sem_take(&sample_sem, K_FOREVER);
/* 真正工作放在執行緒做(可打印、可運算、可呼叫可能阻塞的 API) */
printk("do sampling & process\n");
}
}
K_THREAD_DEFINE(tw, 1024, worker, NULL, NULL, NULL, 3, 0, 0);
void main(void)
{
k_timer_start(&tmr, K_MSEC(10), K_MSEC(10));
}
```
*重點*:
* `k_sem_give()` 可以在 ISR 呼叫;mutex 則不行。
* 用 semaphore 來「喚醒/排隊工作」,比在 ISR 裡做重活安全太多。
---
## 3) Semaphore 範例 B:計數型(resource pool)
**情境**:系統同時最多允許 3 個 DMA buffer 被使用;第 4 個嘗試會阻塞等前面有人釋放。
```c
/* 初始可用 3 個,最大 3 個 */
K_SEM_DEFINE(buf_sem, 3, 3);
void use_buffer(void)
{
/* 沒名額就等待 */
k_sem_take(&buf_sem, K_FOREVER);
/* 使用 buffer ... */
k_msleep(5);
/* 用完歸還名額 */
k_sem_give(&buf_sem);
}
```
---
## 何時選誰?(決策清單)
* **保護共享資料的臨界區** → 用 **Mutex**(較安全、可讀性高、有優先權繼承)。
* **ISR/裝置 → 執行緒的喚醒/通知** → 用 **Semaphore**。
* **固定數量資源池**(N 個「可借用的東西」) → 用 **Counting Semaphore**。
* **千萬別**在 ISR 用 mutex;也不要用 semaphore 硬當互斥鎖。
---
## Zephyr 常用 API 速查
* **Mutex**:
* 宣告:`K_MUTEX_DEFINE(m);` 或 `struct k_mutex m; k_mutex_init(&m);`
* 上鎖:`k_mutex_lock(&m, K_FOREVER /* or timeout */);`
* 解鎖:`k_mutex_unlock(&m);`
* **Semaphore**:
* 宣告:`K_SEM_DEFINE(s, initial, limit);` 或 `k_sem_init(&s, initial, limit);`
* 取得:`k_sem_take(&s, K_FOREVER /* or timeout */);`
* 釋放:`k_sem_give(&s);`(可在 ISR)
---
## 常見地雷
* 在 callback/ISR 做重活(印大量 log、演算法、寫 Flash)→ 改成 **給 semaphore** 或 **丟 `k_work`**。
* 用 semaphore 當互斥鎖 → 容易誤用、沒優先權繼承;**改用 mutex**。
* 忘了 `unlock` / 多次 `unlock` → 形成死鎖或不對稱;務必用結構化範圍包住臨界區。
* 超時處理 → `k_mutex_lock(&m, K_MSEC(…))` 或 `k_sem_take(&s, K_MSEC(…))`,逾時要記得做補救。
---
參考文章來源:
https://academy.nordicsemi.com/courses/nrf-connect-sdk-fundamentals/lessons/lesson-8-thread-synchronization/topic/the-need-for-thread-synchronization/
> [time=Fri, Dec 5, 2025 3:06 PM]