---
# System prepended metadata

title: Thread Synchronization（執行緒同步）

---



# 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 的示意圖](https://academy.nordicsemi.com/wp-content/uploads/2022/02/semaphore.png)
> 圖一、Semaphore 的示意圖
> ![Mutex 的示意圖](https://academy.nordicsemi.com/wp-content/uploads/2022/02/mutex.png)
> 圖二、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]