Perché pthread_cond_wait ha wakeups spurie?

Per citare la pagina man:

Quando si usano variabili di condizione, esiste sempre un predicato booleano che coinvolge variabili condivise associate ad ogni condizione di attesa che è vera se il thread deve procedere. Potrebbero verificarsi ritriggerszioni spurie dalle funzioni pthread_cond_timedwait () o pthread_cond_wait (). Poiché il ritorno da pthread_cond_timedwait () o pthread_cond_wait () non implica nulla sul valore di questo predicato, il predicato dovrebbe essere rivalutato al ritorno.

Quindi, pthread_cond_wait può tornare anche se non lo hai segnalato. A prima vista, almeno sembra abbastanza atroce. Sarebbe come una funzione che restituisce in modo casuale il valore errato o restituito in modo casuale prima che raggiungesse effettivamente un’istruzione di ritorno corretta. Sembra un bug importante. Ma il fatto che abbiano scelto di documentarlo nella pagina man piuttosto che sistemarlo sembrerebbe indicare che esiste una ragione legittima per cui pthread_cond_wait finisce svegliandosi spurio. Presumibilmente, c’è qualcosa di intrinseco nel modo in cui funziona che lo rende così che non può essere aiutato. La domanda è cosa.

Perché pthread_cond_wait restituisce spurio? Perché non può garantire che si svegli solo quando è stato segnalato correttamente? Qualcuno può spiegare il motivo del suo comportamento spuria?

La seguente spiegazione è fornita da David R. Butenhof in “Programmazione con i thread POSIX” (pagina 80):

Sveglie spurie possono sembrare strane, ma su alcuni sistemi multiprocessore, il fatto che la condizione di sveglia sia completamente prevedibile potrebbe rallentare in modo sostanziale tutte le operazioni delle variabili di condizione.

Nella seguente discussione di comp.programming.threads , espande il pensiero dietro al design:

 Patrick Doyle ha scritto: 
 > Nell'articolo, Tom Payne ha scritto: 
 >> Kaz Kylheku ha scritto: 
 >>: È così perché a volte le implementazioni non possono evitare di inserire 
 >>: questi risvegli spuri;  potrebbe essere costoso prevenirli. 

 >> Ma perché?  perchè è così difficile?  Ad esempio, stiamo parlando 
 >> situazioni in cui un'attesa scade proprio quando arriva un segnale? 

 > Sai, mi chiedo se i progettisti di pthread abbiano usato la logica in questo modo: 
 > gli utenti di variabili di condizione devono comunque verificare la condizione all'uscita, 
 > quindi non imporremo alcun onere aggiuntivo su di loro se consentiamo 
 > wakeups spurie;  e dal momento che è ipotizzabile consentire il falso 
 > i wakeup potrebbero rendere più veloce un'implementazione, può solo aiutare se noi 
 > permetti loro. 

 > Potrebbero non aver avuto alcuna implementazione particolare in mente. 

 In realtà non sei affatto lontano, tranne che non l'hai spinto abbastanza lontano. 

 L'intento era quello di forzare il codice corretto / robusto richiedendo cicli di predicati.  Questo era 
 guidato dal contingente accademico provatamente corretto tra i "core thread" in 
 il gruppo di lavoro, anche se non penso che nessuno sia davvero in disaccordo con l'intento 
 una volta capito cosa significasse. 

 Abbiamo seguito questo intento con diversi livelli di giustificazione.  Il primo era quello 
 "religiosamente" l'uso di un loop protegge l'applicazione dal proprio imperfetto 
 pratiche di codifica.  Il secondo era che non era difficile immaginarselo astrattamente 
 macchine e codice di implementazione che potrebbero sfruttare questo requisito per migliorare 
 le prestazioni della condizione media attendono le operazioni attraverso l'ottimizzazione del 
 meccanismi di sincronizzazione. 
 / ------------------ [David.Buten ... @ compaq.com] ------------------ \ 
 |  Architetto di thread POSIX Compaq Computer Corporation | 
 |  Il mio libro: http://www.awl.com/cseng/titles/0-201-63392-2/ | 
 \ ----- [http://home.earthlink.net/~anneart/family/dave.html] ----- / 

Ci sono almeno due cose che “wakeful spurie” possono significare:

  • Un thread bloccato in pthread_cond_wait può tornare dalla chiamata anche se non si è verificata alcuna chiamata a segnalare o trasmettere sulla condizione.
  • Un thread bloccato in pthread_cond_wait ritorna a causa di una chiamata al segnale o broadcast, tuttavia dopo aver riacquisito il mutex, il predicato sottostante non risulta più vero.

Ma quest’ultimo caso può verificarsi anche se l’implementazione della variabile di condizione non consente il primo caso. Prendi in considerazione una coda dei consumatori e tre thread.

  • Il thread 1 ha appena rimosso un elemento e rilasciato il mutex e la coda è vuota. Il thread sta facendo tutto ciò che fa con l’elemento che ha acquisito su alcune CPU.
  • Thread 2 tenta di dequeue un elemento, ma trova la coda vuota quando viene selezionata sotto il mutex, chiama pthread_cond_wait e blocca nella chiamata in attesa di segnale / trasmissione.
  • Thread 3 ottiene il mutex, inserisce un nuovo elemento nella coda, notifica la variabile condition e rilascia il lock.
  • In risposta alla notifica dal thread 3, il thread 2, che era in attesa della condizione, è pianificato per l’esecuzione.
  • Tuttavia, prima che il thread 2 riesca ad accedere alla CPU e ad afferrare il blocco della coda, il thread 1 completa l’attività corrente e ritorna in coda per ulteriore lavoro. Ottiene il blocco della coda, controlla il predicato e scopre che c’è lavoro nella coda. Procede a deselezionare l’elemento inserito nel thread 3, rilascia il blocco e fa tutto ciò che fa con l’elemento che il thread 3 ha accodato.
  • Il thread 2 ora ottiene una CPU e ottiene il blocco, ma quando controlla il predicato, rileva che la coda è vuota. Il thread 1 “ha rubato” l’object, quindi il wakeup sembra essere spurio. Thread 2 deve attendere di nuovo la condizione.

Quindi, poiché è sempre necessario controllare il predicato in un ciclo, non fa alcuna differenza se le variabili di condizione sottostanti possono avere altri tipi di wakeup spurie.

La sezione “Risvegli multipli per segnale di condizione” in pthread_cond_signal ha un’implementazione di esempio di pthread_cond_wait e pthread_cond_signal che implica il wakefulup spurio.