Perché pthread_cond_wait ha risvegli spuri?


145

Per citare la pagina man:

Quando si utilizzano le variabili di condizione, esiste sempre un predicato booleano che coinvolge le variabili condivise associate a ciascuna condizione di attesa, che è vero se il thread deve procedere. Possono verificarsi attivazioni spurie dalle funzioni pthread_cond_timedwait () o pthread_cond_wait (). Poiché il ritorno da pthread_cond_timedwait () o pthread_cond_wait () non implica nulla riguardo al valore di questo predicato, il predicato dovrebbe essere rivalutato su tale ritorno.

Quindi, pthread_cond_waitpuò tornare anche se non lo hai segnalato. Almeno a prima vista, sembra abbastanza atroce. Sarebbe come una funzione che ha restituito in modo casuale il valore errato o restituito in modo casuale prima che raggiungesse effettivamente un'istruzione di ritorno corretta. Sembra un grosso errore. Ma il fatto che abbiano scelto di documentare questo nella pagina man piuttosto che risolverlo sembrerebbe indicare che esiste un motivo legittimo per cui pthread_cond_waitfinisce per svegliarsi spuri. Presumibilmente, c'è qualcosa di intrinseco nel modo in cui funziona che lo rende in modo che non possa essere aiutato. La domanda è cosa.

Perché non pthread_cond_waittornare spurio? Perché non può garantire che si sveglierà solo quando è stato correttamente segnalato? Qualcuno può spiegare il motivo del suo comportamento spurio?


5
Immagino che abbia qualcosa a che fare con il ritorno ogni volta che il processo rileva un segnale. La maggior parte dei * nix non riavvia una chiamata bloccante dopo che un segnale la interrompe; hanno semplicemente impostato / restituito un codice di errore che indica che si è verificato un segnale.
cHao,

1
@cHao: sebbene si noti che poiché le variabili di condizione hanno comunque altri motivi per risvegli spuri, la gestione di un segnale non è un errore per pthread_cond_(timed)wait: "Se viene inviato un segnale ... il thread riprende in attesa della variabile di condizione come se fosse non interrotto o restituirà zero a causa di un risveglio spurio ". Altre funzioni di blocco indicano EINTRquando sono interrotte da un segnale (ad es. read) O sono richieste per riprendere (ad es pthread_mutex_lock.). Quindi se non ci fossero altre ragioni per un risveglio spurio, si pthread_cond_waitsarebbe potuto definire come uno di questi.
Steve Jessop,

4
Un articolo correlato su Wikipedia: sveglia spuria
Palec


Molte funzioni non possono svolgere completamente il loro lavoro (I / O interrotto) e le funzioni di osservazione possono ricevere non eventi come una modifica a una directory in cui la modifica è stata annullata o ripristinata. Qual è il problema?
curiousguy,

Risposte:


77

La seguente spiegazione è data da David R. Butenhof in "Programmazione con thread POSIX" (p. 80):

Le riattivazioni spurie possono sembrare strane, ma su alcuni sistemi multiprocessore, rendere completamente prevedibile la riattivazione delle condizioni potrebbe rallentare sostanzialmente tutte le operazioni delle variabili di condizione.

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

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

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

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

> Potrebbero non aver avuto in mente particolari implementazioni. 

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

L'intenzione era quella di forzare il codice corretto / robusto richiedendo cicli predicati. Questo era
guidato dal contingente accademico dimostrabilmente corretto tra i "thread principali" in 
il gruppo di lavoro, anche se non credo che nessuno sia davvero in disaccordo con l'intento 
una volta capito cosa significava. 

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


22
fondamentalmente questo non dice nulla. Qui non viene fornita alcuna spiegazione oltre al pensiero iniziale che "potrebbe rendere le cose più veloci", ma nessuno sa come o se lo fa affatto.
Bogdan Ionitza,

107

Ci sono almeno due cose che "risveglio spurio" potrebbe significare:

  • Un thread bloccato pthread_cond_waitpuò tornare dalla chiamata anche se non si è verificata alcuna chiamata pthread_call_signalo pthread_cond_broadcastsulla condizione.
  • Un thread bloccato nei pthread_cond_waitritorni a causa di una chiamata a , pthread_cond_signalo pthread_cond_broadcastcomunque dopo aver riacquistato il mutex il predicato sottostante non è più vero.

Ma quest'ultimo caso può verificarsi anche se l'implementazione della variabile condizione non consente il primo caso. Considera una coda del consumatore del produttore e tre thread.

  • Il thread 1 ha appena rimosso la coda di un elemento e rilasciato il mutex, e la coda ora è vuota. Il thread sta facendo tutto ciò che fa con l'elemento acquisito su alcune CPU.
  • Il thread 2 tenta di dequeue un elemento, ma trova vuota la coda quando viene selezionata sotto il mutex, chiama pthread_cond_waite blocca la chiamata in attesa di segnale / trasmissione.
  • Il thread 3 ottiene il mutex, inserisce un nuovo elemento nella coda, notifica la variabile di condizione e rilascia il blocco.
  • 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 la sua attività corrente e ritorna in coda per ulteriori lavori. Ottiene il blocco della coda, controlla il predicato e trova che c'è lavoro nella coda. Procede a dequeue l'elemento inserito nel thread 3, rilascia il blocco e fa tutto ciò che fa con l'elemento che il thread 3 ha accodato.
  • Thread 2 ora ottiene su una CPU e ottiene il blocco, ma quando controlla il predicato, scopre che la coda è vuota. Il thread 1 ha "rubato" l'oggetto, quindi il risveglio sembra essere falso. Il thread 2 deve attendere nuovamente sulla condizione.

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


23
sì. Essenzialmente, questo è ciò che accade quando viene utilizzato un evento invece di un meccanismo di sincronizzazione con un conteggio. Purtroppo, sembra che anche i semafori POSIX (su Linux) siano soggetti a risvegli spuri. Trovo un po 'strano che un errore funzionale fondamentale delle primitive di sincronizzazione sia appena accettato come' normale 'e debba essere aggirato a livello di utente :( Presumibilmente, gli sviluppatori sarebbero armati se una chiamata di sistema fosse documentata con una sezione "Segreto spurio" o, forse, "Spurio che si collega all'URL sbagliato" o "Apertura spuria del file sbagliato".
Martin James,

2
Lo scenario più comune di un "risveglio spurio" è probabilmente l'effetto collaterale di una chiamata a pthread_cond_broadcast (). Diciamo che hai un pool di 5 thread, due si svegliano alla trasmissione e fanno il lavoro. Gli altri tre si svegliano e scoprono che il lavoro è stato fatto. I sistemi multiprocessore possono anche provocare un segnale condizionale che risveglia accidentalmente più thread. Il codice controlla di nuovo il predicato, vede uno stato non valido e torna in sospensione. In entrambi i casi, la verifica del predicato risolve il problema. IMO, in generale, gli utenti non dovrebbero usare mutex e condizionali POSIX grezzi.
CubicleSoft,

1
@MartinJames - Che ne dici del classico "spurio" EINTR? Concordo sul fatto che testare costantemente EINTR in un ciclo è un po 'fastidioso e rende il codice piuttosto brutto, ma gli sviluppatori lo fanno comunque per evitare rotture casuali.
CubicleSoft

2
@Yola No, non puoi, perché dovresti bloccare un mutex attorno a pthread_cond_signal/broadcaste non sarai in grado di farlo, fino a quando il mutex non viene sbloccato chiamando pthread_cond_wait.
a3f,

1
L'esempio di questa risposta è molto realistico e concordo sul fatto che il controllo dei predicati sia una buona idea. Tuttavia, non è stato possibile risolverlo in modo equo prendendo il passaggio problematico "il thread 1 completa l'attività corrente e torna alla coda per ulteriori lavori" e sostituendolo con "il thread 1 completa l'attività corrente e torna in attesa la variabile di condizione "? Ciò eliminerebbe la modalità di errore descritta nella risposta, e sono abbastanza sicuro che renderebbe il codice corretto, in assenza di risvegli spuri . Esiste una reale implementazione che produca risvegli spuri nella pratica?
Quuxplusone

7

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 coinvolge risvegli spuri.


2
Penso che questa risposta sia sbagliata, per quanto va. L'implementazione di esempio in quella pagina ha un'implementazione di "notifica uno" che equivale a "notifica tutto"; ma non sembra generare in realtà risvegli spuri . L'unico modo per riattivare un thread è tramite un altro thread che invoca "notifica tutto" o un altro thread che richiama l'etichetta "notifica uno" che è "notifica tutto".
Quuxplusone,

5

Anche se non credo che sia stato preso in considerazione al momento della progettazione, ecco un vero motivo tecnico: in combinazione con la cancellazione del thread, ci sono condizioni in cui l'opzione di svegliarsi "spuratamente" può essere assolutamente necessaria, almeno a meno che tu non sei disposto a imporre vincoli molto molto forti su quale tipo di strategie di attuazione sono possibili.

Il problema chiave è che, se un thread agisce sulla cancellazione mentre è bloccato pthread_cond_wait, gli effetti collaterali devono essere come se non consumassero alcun segnale sulla variabile condizione. Tuttavia, è difficile (e fortemente vincolante) assicurarsi di non aver già consumato un segnale quando si inizia ad agire in caso di annullamento, e in questa fase potrebbe essere impossibile "ripubblicare" il segnale nella variabile di condizione, poiché è possibile essere in una situazione in cui il chiamante pthread_cond_signalè già giustificato per aver distrutto la condvar e liberato la memoria in cui risiedeva.

L'indennità per la scia falsa ti dà una facile uscita. Invece di continuare ad agire in caso di cancellazione quando arriva mentre è bloccato su una variabile di condizione, se potresti aver già consumato un segnale (o se vuoi essere pigro, non importa quale), puoi invece dichiarare che si è verificata una scia falsa, e tornare con successo. Ciò non interferisce affatto con l'operazione di cancellazione, poiché un chiamante corretto agirà semplicemente sulla cancellazione in sospeso la volta successiva che si avvia e chiama di pthread_cond_waitnuovo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.