Example 1: 等待條件成立

基本使用模式

pthread_mutex_t m;
pthread_cond_t v;
int x, y;
 
// Thread 1: 等待 x==y 條件
pthread_mutex_lock(&m);
while (x != y)
    pthread_cond_wait(&v, &m);
// do something here if necessary
pthread_mutex_unlock(&m);
 
// Thread 2: 通知等待的 thread x 已經被改變
pthread_mutex_lock(&m);
x++;
pthread_mutex_unlock(&m);
pthread_cond_signal(&v);

關鍵問題:Signal 和 Unlock 的順序

問題

在 Thread 2 中,如果 x 在 pthread_mutex_unlock(&m) 之後、pthread_cond_signal(&v) 之前被改變會怎麼樣?

答案

這樣是安全的,因為:

  • pthread_cond_wait() 在被喚醒後會重新檢查條件 (使用 while loop)
  • 即使 signal 和實際喚醒之間有延遲,while loop 會確保條件確實成立

Example 3: Thread 同步

兩個 Thread 之間的同步

int done = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t c = PTHREAD_COND_INITIALIZER;
 
void *spawned(void *arg) {
    // spawned thread goes first
    pthread_mutex_lock(&m);
    done = 1;
    pthread_cond_signal(&c);
    pthread_mutex_unlock(&m);
    return NULL;
}
 
int main(int argc, char *argv[]) {
    pthread_t p;
    pthread_create(&p, NULL, spawned, NULL);
    pthread_mutex_lock(&m);
    while (done == 0)
        pthread_cond_wait(&c, &m);
    pthread_mutex_unlock(&m);
    // main thread continues
}

Signal 或 Unlock 先執行?

兩種順序都可以,但有細微差異:

  1. 先 Signal 再 Unlock (如範例所示)

    • 被喚醒的 thread 會立即嘗試獲取 mutex,但因為還被 lock 住所以會等待
    • 當 unlock 後,等待的 thread 才能獲得 mutex 並繼續執行
  2. 先 Unlock 再 Signal

    • 被喚醒的 thread 有機會立即獲取 mutex
    • 可能效率稍微好一點,但差異不大

常見錯誤與 Race Conditions

錯誤 1: 移除 done 變數

void *spawned(void *arg) {
    pthread_mutex_lock(&m);
    // done = 1;  // 被移除!
    pthread_cond_signal(&c);
    pthread_mutex_unlock(&m);
    return NULL;
}
 
int main(int argc, char *argv[]) {
    pthread_t p;
    pthread_create(&p, NULL, spawned, NULL);
    pthread_mutex_lock(&m);
    // while (done == 0)  // 被移除!
        pthread_cond_wait(&c, &m);
    pthread_mutex_unlock(&m);
}

問題:

  • Race Condition! spawned thread 會發送 signal,但可能此時 main thread 還沒開始等待
  • 如果 spawned thread 先執行並發送 signal,但當時沒有任何 thread 在等待這個 condition
  • Main thread 之後呼叫 pthread_cond_wait() 時會永遠睡著,因為 signal 已經發送過了

教訓: 需要一個 state variable (如 done) 來記錄狀態,避免錯過 signal


錯誤 2: 移除 Mutex

void *spawned(void *arg) {
    // pthread_mutex_lock(&m);  // 被移除!
    done = 1;
    pthread_cond_signal(&c);
    // pthread_mutex_unlock(&m);  // 被移除!
    return NULL;
}
 
int main(int argc, char *argv[]) {
    pthread_t p;
    pthread_create(&p, NULL, spawned, NULL);
    // pthread_mutex_lock(&m);  // 被移除!
    while (done == 0)
        pthread_cond_wait(&c, &m);
    // pthread_mutex_unlock(&m);  // 被移除!
}

問題:

  • Race Condition! spawned thread 可能在 main thread 檢查 done == 0 之後、呼叫 pthread_cond_wait() 之前就發送 signal
  • 執行順序可能是:
    1. Main thread 檢查 done == 0 (條件成立)
    2. Spawned thread 設定 done = 1 並發送 signal
    3. Main thread 呼叫 pthread_cond_wait() 開始等待
  • 結果:Main thread 會永遠睡著,因為 signal 已經在它開始等待之前就發送了

教訓: Mutex 必須用來保護 condition check 和 pthread_cond_wait() 之間的操作,確保它們是 atomic 的


重要觀念總結

為什麼需要 Mutex?

  • 保護 shared state: Condition variable 通常搭配某個 state variable 使用 (如 done, workq)
  • 避免 race condition: 確保檢查條件和進入等待之間的操作是 atomic 的
  • pthread_cond_wait() 的特殊行為:
    • 呼叫時會 atomically unlock mutex 並進入睡眠
    • 被喚醒時會重新 lock mutex 才返回

為什麼用 while 而不是 if?

while (condition_not_met)  // 建議使用 while
    pthread_cond_wait(&c, &m);
 
// 而不是
if (condition_not_met)  // 不建議
    pthread_cond_wait(&c, &m);

原因:

  • Spurious wakeups: Thread 可能被意外喚醒 (不是因為 signal)
  • Multiple waiters: 多個 thread 等待同一個 condition 時,被喚醒後條件可能已經被其他 thread 改變
  • 安全性: 使用 while loop 確保條件確實成立才繼續執行

Signal vs Broadcast

  • pthread_cond_signal(&c): 喚醒一個等待的 thread
  • pthread_cond_broadcast(&c): 喚醒所有等待的 thread
  • 當多個 thread 等待同一個 condition 時,broadcast 確保至少有一個能夠處理