1.Linux對(duì)虛假喚醒的說(shuō)明
On a multi-processor, it may be impossible for an implementation of pthread_cond_signal() to avoid the unblocking of more than one thread blocked on a condition variable.
The effect is that more than one thread can return from its call to pthread_cond_wait() or pthread_cond_timedwait() as a result of one call to pthread_cond_signal(). This effect is called “spurious wakeup”. Note that the situation is self-correcting in that the number of threads that are so awakened is finite; for example, the next thread to call pthread_cond_wait() after the sequence of events above blocks.
While this problem could be resolved, the loss of efficiency for a fringe condition that occurs only rarely is unacceptable, especially given that one has to check the predicate associated with a condition variable anyway. Correcting this problem would unnecessarily reduce the degree of concurrency in this basic building block for all higher-level synchronization operations.
在多核處理器下,pthread_cond_signal可能會(huì)激活多于一個(gè)線程(阻塞在條件變量上的線程)。結(jié)果是,當(dāng)一個(gè)線程調(diào)用pthread_cond_signal()后,多個(gè)調(diào)用pthread_cond_wait()或pthread_cond_timedwait()的線程返回。這種效應(yīng)成為”虛假喚醒”(spurious wakeup)。
雖然虛假喚醒在pthread_cond_wait函數(shù)中可以解決,為了發(fā)生概率很低的情況而降低邊緣條件(fringe condition)效率是不值得的,糾正這個(gè)問(wèn)題會(huì)降低對(duì)所有基于它的所有更高級(jí)的同步操作的并發(fā)度。所以pthread_cond_wait的實(shí)現(xiàn)上沒(méi)有去解它。通常的解決方法是將if改為while
static void *thread_func(void *arg)
{
while (1) {
pthread_mutex_lock(&mtx); //這個(gè)mutex主要是用來(lái)保證pthread_cond_wait的并發(fā)性
while (msg_list.empty()) { //pthread_cond_wait里的線程可能會(huì)被意外喚醒(虛假喚醒),如果這個(gè)時(shí)候,則不是我們想要的情況。這個(gè)時(shí)候,應(yīng)該讓線程繼續(xù)進(jìn)入pthread_cond_wait
pthread_cond_wait(&cond, &mtx);
}
msg = msg_list.pop();
pthread_mutex_unlock(&mtx); //臨界區(qū)數(shù)據(jù)操作完畢,釋放互斥鎖
// handle msg
}
return 0;
}
2.其他對(duì)虛假喚醒的說(shuō)明
An added benefit of allowing spurious wakeups is that applications are forced to code a predicate-testing-loop around the condition wait. This also makes the application tolerate superfluous condition broadcasts or signals on the same condition variable that may be coded in some other part of the application. The resulting applications are thus more robust. Therefore, IEEE Std 1003.1-2001 explicitly documents that spurious wakeups may occur.
- 在linux對(duì)條件變量的描述認(rèn)為spurious wakeup是允許的,添加while檢查的做法被認(rèn)為是增加了程序的健壯性。
but on some multiprocessor systems, making condition wakeup completely predictable might substantially slow all condition variable operations. The race conditions that cause spurious wakeups should be considered rare.
在POSIX Threads中:
David R. Butenhof 認(rèn)為多核系統(tǒng)中 條件競(jìng)爭(zhēng)(race condition)導(dǎo)致了虛假喚醒的發(fā)生,并且認(rèn)為完全消除虛假喚醒本質(zhì)上會(huì)降低了條件變量的操作性能,因?yàn)樘摷賳拘寻l(fā)生的概率發(fā)生很小。Effective java 曾經(jīng)提到Item 50: Never invoke wait outside a loop.
3.linux中為什么要設(shè)置虛假喚醒
在linux中,pthread_cond_wait底層是futex系統(tǒng)調(diào)用。在linux中,任何慢速的阻塞的系統(tǒng)調(diào)用當(dāng)接收到信號(hào)的時(shí)候,就會(huì)返回-1,并且設(shè)置errno為EINTR。在系統(tǒng)調(diào)用返回前,用戶程序注冊(cè)的信號(hào)處理函數(shù)會(huì)被調(diào)用處理。
- 注:什么有樣的系統(tǒng)調(diào)用會(huì)出現(xiàn)接收信號(hào)后發(fā)揮EINTR呢?
慢速阻塞的系統(tǒng)調(diào)用,有可能會(huì)永遠(yuǎn)阻塞下去的那種。當(dāng)接收到信號(hào)的時(shí)候,認(rèn)為是一個(gè)返回并執(zhí)行其他代碼的一個(gè)時(shí)機(jī)。
信號(hào)的處理也不簡(jiǎn)單,因?yàn)橛行┞到y(tǒng)調(diào)用被信號(hào)中斷后是會(huì)自動(dòng)重啟的,所以我們通常需要用siginterrupt(signo, 1)來(lái)關(guān)閉重啟或者在用sigaction安裝信號(hào)處理函數(shù)的時(shí)候取消SA_RESTART標(biāo)志,之后就可以通過(guò)判斷信號(hào)的返回值是否是-1和errno是否為EINTR來(lái)判斷是否有信號(hào)抵達(dá)。
如果關(guān)閉了SA_RESTART的一些使用慢速系統(tǒng)調(diào)用的應(yīng)用,一般都采用while()循環(huán),檢測(cè)到EINTR后就重新調(diào)用。