2013-04-08 9 views
13

Devo sincronizzare std::condition_variable/condition_variable_any::notify_one?Devo sincronizzare std :: condition_variable/condition_variable_any :: notify_one

Per quanto posso vedere, se la perdita di notifiche è accettabile, è corretto chiamare notify_one non protetto (da mutex ad esempio).

Per esempio, ho visto seguendo modelli di utilizzo (mi dispiace, non ricordo dove):

{ 
    { 
     lock_guard<mutex> l(m); 
     // do work 
    } 
    c.notify_one(); 
} 

Ma, ho controllato le fonti libstdC++, e vedo:

condition_variable::notify_one

void condition_variable::notify_one() noexcept 
{ 
    int __e = __gthread_cond_signal(&_M_cond); 
    // XXX not in spec 
    // EINVAL 
    if (__e) 
     __throw_system_error(__e); 
} 

e condition_variable_any::notify_one:

void condition_variable_any::notify_one() noexcept 
{ 
    lock_guard<mutex> __lock(_M_mutex); 
    _M_cond.notify_one(); 
} 

E qui è il layout di condition_variable_any:

class condition_variable_any 
{ 
    condition_variable _M_cond; 
    mutex _M_mutex; 
    // data end 

Vale a dire è solo un involucro sottile attorno a condition_variable + mutex.

Quindi, domande:

  1. E 'thread-safe per non protegge notify_one da mutex sia per condition_variable_any o condition_variable?
  2. Perché l'implementazione di condition_variable_any utilizza mutex aggiuntivo?
  3. Perché l'implementazione di condition_variable_any::notify_one e condition_variable::notify_one differisce? Forse condition_variable::notify_one richiede la protezione manuale ma non lo è il condition_variable_any::notify_one? È un bug libstdC++?

risposta

13

I.e. è solo un involucro sottile attorno a condition_variable + mutex.

Er, n. Solo perché ha membri di quei tipi non ne fa un involucro sottile. Cerca di capire cosa fa realmente, non solo i tipi dei suoi membri privati. C'è un codice piuttosto sottile lì.

  1. E 'thread-safe di non proteggere i notify_one da mutex sia per condition_variable_any o condition_variable?

Sì.

In effetti, chiamando lo 01excon il mutex bloccato causerà l'attivazione dei thread in attesa, tenterà di bloccare il mutex, lo troverà ancora bloccato dal thread di notifica e tornerà a dormire fino a quando il mutex non verrà rilasciato.

Se si chiama notify_one() senza il mutex bloccato, i thread di veglia possono essere eseguiti immediatamente.

2 Perché l'implementazione di condition_variable_any utilizza mutex aggiuntivo?

condition_variable_any può essere utilizzato con qualsiasi tipo bloccabile, non solo std:mutex, ma internamente l'una in libstdC++ utilizza un condition_variable, che può essere utilizzato solo con std::mutex, quindi ha un std::mutex oggetto interno troppo.

Quindi il condition_variable_any funziona con due mutex, quello esterno fornito dall'utente e quello interno utilizzato dall'implementazione.

3 Perché l'implementazione di condition_variable_any :: notify_one e condition_variable :: notify_one differisce? Forse condition_variable :: notify_one richiede la protezione manuale ma condition_variable_any :: notify_one no? È un bug libstdC++?

No, non è un bug.

Lo standard richiede che la chiamata wait(mx) debba sbloccare atomicamente mx e attendere. libstdC++ usa il mutex interno per fornire quella garanzia di atomicità. Il mutex interno deve essere bloccato per evitare le notifiche mancate se altri thread stanno per aspettare sullo condition_variable_any.

+0

Grazie! Questo mi ha aiutato molto. Esattamente rispondo a 2 e 3 - buon punto sull'atomicità, e cose interiori che usano comunque il mutex. A proposito, puoi aggiungere il link a pthread_cond_wait http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_cond_wait.html per mostrare che l'implementazione più comune funziona solo con il proprio mutex in internals. Potresti per favore chiarire cosa intendi esattamente in "c'è un codice piuttosto sottile lì". – qble

+0

Riguardo a 1. - può condizione non bloccata condition_variable :: notify_one portare a notifiche mancate? Cioè thread # 1 fa il lavoro sotto mutex, invia risultato, sblocca mutex, [nel frattempo] il thread # 2 blocca mutex ma non ancora chiamato wait, [nel frattempo] thread # 1 chiama notify_one, [nel frattempo] thread # 2 chiama wait - la notifica è persa. – qble

+0

Questa non è una notifica persa, è il thread in attesa che non controlla il predicato sulla condizione prima di attendere. Il blocco del mutex non aiuta questa situazione, la notifica potrebbe ancora arrivare prima che il thread in attesa blocchi il mutex. È ** necessario ** controllare il predicato associato quando si attende una variabile di condizione –

2

(1) Non vedo alcun motivo per cui la segnalazione di una variabile di condizione debba essere sorvegliata da un mutex, da un punto di riferimento della corsa dei dati. Ovviamente, hai la possibilità di ricevere notifiche ridondanti o di perdere notifiche, ma se questa è una condizione di errore accettabile o recuperabile per il tuo programma, non credo ci sia nulla nello standard che lo renda illegale. Lo standard, ovviamente, non ti proteggerà dalle condizioni della gara; è responsabilità del programmatore assicurarsi che le condizioni di gara siano benigne. (E, naturalmente, è essenziale che il programmatore non inserisca alcuna "corsa di dati", definita in modo specifico nello standard ma non si applichi direttamente alle primitive di sincronizzazione, oppure viene richiamato il comportamento non definito.)

(2) Non posso rispondere a una domanda come questa sull'implementazione interna di una struttura di libreria standard. Ovviamente, è responsabilità del fornitore fornire servizi di libreria che funzionino correttamente e che soddisfino le specifiche. L'implementazione di questa libreria può avere uno stato interno che richiede l'esclusione reciproca per evitare la corruzione, oppure può eseguire il blocco per evitare notifiche perse o ridondanti. (Solo perché il tuo programma può tollerarli, non significa che gli utenti arbitrari della libreria possono, e in generale, mi aspetto che non possano farlo.) Sarebbe solo una mia speculazione su cosa stanno proteggendo con questo mutex.

(3) condition_variable_any è progettato per funzionare su qualsiasi oggetto simile a un lucchetto, mentre condition_variable è progettato specificamente per funzionare con unique_lock<mutex>.Quest'ultimo è probabilmente più facile da implementare e/o più performante rispetto al primo, dal momento che conosce in modo specifico su quali tipi sta operando e cosa richiedono (se sono banali, se si adattano a una linea cache, se si collegano direttamente a una serie specifica di syscalls, quali recinzioni o coerenza della cache garantisce implicano, ecc.), mentre la prima fornisce una funzionalità generica per operare su oggetti lock-ish senza essere bloccati specificamente con i vincoli di std::mutex o std::unique_lock<>.

+0

Forse dovrei aggiungere a Q: l'implementazione di condition_variable_any è solo un involucro sottile attorno a condition_variable + mutex. – qble

+0

"Solo perché il tuo programma può tollerarli, non significa che gli utenti arbitrari della libreria possono, e in generale, mi aspetto che non possano" - Penso che tu non capisca il mio punto - se non guardo la mia notify_one da mutex da solo - ci possono essere notifiche perse, ma se lo custodisco - dovrebbero essere impossibili. L'implementazione non dovrebbe preoccuparsi delle notifiche perse quando l'utente non protegge notify_one by mutex. – qble

+0

@qble: Ancora una volta, non posso davvero commentare ciò che un fornitore di librerie standard sceglie di fare per implementare la loro implementazione dello standard. Sono autorizzati a usare qualsiasi magia del compilatore per farlo funzionare (compresi comportamenti non definiti, se capiscono che la loro piattaforma lo definisce) e possono trarre vantaggio da conoscenze molto specifiche. Potrebbe essere che sappiano che std :: lock_guard agisce abbastanza atomicamente per loro, ma i tipi associati ai parametri del modello potrebbero non esserlo. Si potrebbe trovare un indizio nella loro implementazione di wait(). –