0%

Condition variable and spurious wakeup

以下的程式碼哪裡有問題

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pthread_mutex_t mutex;
pthread_cond_t cond;
std::queue<int> q;
int Get()
{
pthread_mutex_lock(&mutex);
if (q.empty())
pthread_cond_wait(&cond, &mutex);
int item = q.front();
pthread_mutex_unlock(&mutex);
return item;
}
void Add(int item)
{
pthread_mutex_lock(&mutex);
q.push(item);
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);
}

Spurious wakeup

所謂的Spurious wakeup就是被Blocking的Thread認為自己滿足條件而被喚醒,而事實上不是這麼一回事。例如

Spurious wakeups may sound strange, but on some multiprocessor systems, making condition wakeup completely predictable might substantially slow all condition variable operations.

由於有Spurious wakeup的發生,為了程式的正確性,需要為此作處理。

Consumer’s Problem

看上面的

1
2
if (q.empty())
pthread_cond_wait(&cond, &mutex);

當Spurious wakeup發生時,這個條件不一定滿足,因此要把if改成while

Producer’s Problem

由於condition variable的signal跟mutex的unlock是獨立事件,因此兩個的順序不重要,正確性都得以保證。不過這樣子的寫法一樣會造成Spurious wakeu的問題

pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond);

分析如下

  1. Thread A starts waiting for items to be added to a threadsafe queue.
  2. Thread B inserts an item on the queue. After unlocking the queue, but before it issues the signal, a context switch occurs.
  3. Thread C inserts an item on the queue, and issues the cvar signal.
  4. Thread A wakes up, and processes both items. It then goes back to waiting on the queue.
  5. Thread B resumes, and signals the cvar.
  6. Thread A wakes up, then immediately goes back to sleep, because the queue is empty.

Reference

用条件变量实现事件等待器的正确与错误做法
– [signal and unlock order