2010-07-08 13 views
13

Si consideri il seguente C++ funzione membro:Sincronizzazione accesso a un valore di ritorno

size_t size() const 
{ 
    boost::lock_guard<boost::mutex> lock(m_mutex); 
    return m_size; 
} 

L'intento qui non è per sincronizzare l'accesso alla variabile membro privato m_size, ma solo per assicurarsi che il chiamante riceve un valore valido per m_size. L'obiettivo è impedire che la funzione restituisca m_size nello stesso momento in cui qualche altro thread sta modificando m_size.

Ma c'è qualche potenziale condizione di competizione nel chiamare questa funzione? Non sono sicuro che il blocco dello stile RAII qui sia adeguato per proteggersi da una condizione di competizione. Supponiamo che il distruttore del lucchetto sia chiamato prima del il valore di ritorno della funzione sia inserito nello stack?

Devo fare qualcosa di simile al seguente per garantire la sicurezza del filo?

size_t size() const 
{ 
    size_t ret; 

    { 
     boost::lock_guard<boost::mutex> lock(m_mutex); 
     ret = m_size; 
    } 

    return ret; 
} 
+0

Non credo che la tua serratura faccia davvero qualcosa. Finché 'm_size' può essere letto nella sua interezza in una singola operazione atomica, otterrai un valore valido. –

+0

Questo non è garantito in realtà. Tuttavia l'uso di 'std :: atomic' che viene fornito con C++ 0x garantirebbe questo senza il blocco. –

+0

@Mike: anche i lucchetti sono una barriera di memoria. Tale è necessario per sincronizzare le cache del processore ecc. – sbi

risposta

12

Entrambi vostro esempio costruisce farà quello che stai cercando. Le seguenti informazioni dallo standard supporta il comportamento che stai cercando (anche nel tuo primo esempio):

12,4/10 distruttori:

distruttori vengono invocati implicitamente ... per un oggetto costruito con automatica durata della memorizzazione (3.7.2) quando il blocco in cui viene creato l'oggetto viene chiuso.

E, 6,6/2 istruzioni di salto (di cui return è uno):

All'uscita da una portata (comunque realizzato), distruttori (12.4) vengono chiamati per tutti gli oggetti costruiti con memorizzazione automatica durata (3.7.2) (oggetti o temporali con nome) dichiarati in tale ambito, nell'ordine inverso della loro dichiarazione. Il trasferimento fuori da un loop, fuori da un blocco o il ritorno a una variabile inizializzata con durata di archiviazione automatica comporta la distruzione di variabili con durata di archiviazione automatica comprese nell'ambito al punto trasferito ma non nel punto trasferito a.

Quindi al punto della variabile returnlock è portata e quindi la dtor non è stato chiamato. Una volta eseguito lo return, viene chiamato il dtor per la variabile lock (rilasciando così il blocco).

1

Il codice iniziale è bene - il distruttore sarà chiamato dopo che il valore di ritorno è stato memorizzato. Questo è il principio su cui opera RAII!

+3

Avete qualche fonte per questo? –

3

La prima variante è sicura, tuttavia non è possibile fare affidamento su questo valore restituito per essere coerente per qualsiasi periodo di tempo. Voglio dire, per esempio, non usare quel valore restituito in un ciclo for per iterare su ciascun elemento, perché la dimensione reale potrebbe cambiare subito dopo il ritorno.

In pratica si può pensare in questo modo: è necessaria una copia del valore di ritorno, altrimenti il ​​distruttore verrebbe chiamato, quindi probabilmente corrompendo qualunque sia il valore di ritorno prima che fosse restituito.

Il distruttore viene chiamato dopo l'estratto conto. Prendete questo esempio equivalente:

#include <assert.h> 

class A 
{ 
public: 
    ~A() 
    { 
     x = 10; 
    } 
    int x; 
}; 

int test() 
{ 
    A a; 
    a.x = 5; 
    return a.x; 
} 

int main(int argc, char* argv[]) 
{ 
    int x = test(); 
    assert(x == 5); 
    return 0; 
} 
+0

Dopo averci pensato, vedo che questo è l'unico modo in cui il comportamento potrebbe funzionare. Ogni 'return expr;' copia implicitamente il valore di 'expr' in un temporaneo, e qualsiasi oggetto in scope può essere riferito a' expr' in modo che debbano essere ancora attivi nel punto in cui viene eseguita la copia. (Le ottimizzazioni potrebbero rimuovere la copia ovviamente, ma il comportamento deve essere ancora "come se" fosse lì.) –

Problemi correlati