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()在被喚醒後會重新檢查條件 (使用whileloop)- 即使 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 先執行?
兩種順序都可以,但有細微差異:
-
先 Signal 再 Unlock (如範例所示)
- 被喚醒的 thread 會立即嘗試獲取 mutex,但因為還被 lock 住所以會等待
- 當 unlock 後,等待的 thread 才能獲得 mutex 並繼續執行
-
先 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 - 執行順序可能是:
- Main thread 檢查
done == 0(條件成立) - Spawned thread 設定
done = 1並發送 signal - Main thread 呼叫
pthread_cond_wait()開始等待
- Main thread 檢查
- 結果: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 確保至少有一個能夠處理