Const significa mean thread-safe in C ++ 11?

Ho sentito che const significa thread-safe in C ++ 11 . È vero?

Ciò significa che const è ora l’equivalente di Java synchronized ?

Stanno finendo le parole chiave ?

Ho sentito che const significa thread-safe in C ++ 11 . È vero?

È piuttosto vero …

Questo è ciò che la lingua standard ha da dire sulla sicurezza del thread:

[1.10 / 4] Due valutazioni di espressione sono in conflitto se una di esse modifica una posizione di memoria (1.7) e l’altra accede o modifica la stessa posizione di memoria.

[1.10 / 21] L’esecuzione di un programma contiene una corsa di dati se contiene due azioni in conflitto in thread diversi, almeno uno dei quali non è atomico, e nessuno dei due avviene prima dell’altro. Qualsiasi razza di dati di questo genere ha un comportamento indefinito.

che non è altro che la condizione sufficiente per una gara di dati che si verifichi:

  1. Ci sono due o più azioni eseguite nello stesso momento su una determinata cosa; e
  2. Almeno uno di loro è una scrittura.

La libreria standard si basa su questo, andando un po ‘oltre:

[17.6.5.9/1] Questa sezione specifica i requisiti che le implementazioni devono soddisfare per prevenire le corse di dati (1.10). Ogni funzione di libreria standard deve soddisfare ogni requisito, se non diversamente specificato. Le implementazioni possono impedire le corse di dati in casi diversi da quelli specificati di seguito.

[17.6.5.9/3] Una funzione di libreria standard C ++ non modificherà direttamente o indirettamente oggetti (1.10) accessibili da thread diversi dal thread corrente, a meno che gli oggetti non siano accessibili direttamente o indirettamente tramite gli argomenti non const della funzione, incluso this .

che in parole semplici dice che si aspetta che le operazioni su oggetti const siano thread-safe . Ciò significa che la libreria standard non introdurrà una corsa di dati purché le operazioni su oggetti const dei propri tipi sia

  1. Consiste interamente di letture – cioè, non ci sono scritture–; o
  2. Sincronizza internamente le scritture.

Se questa aspettativa non è valida per uno dei tuoi tipi, quindi usarlo direttamente o indirettamente insieme a qualsiasi componente della Libreria standard può comportare una corsa di dati . In conclusione, const non significa thread-safe dal punto di vista della Libreria Standard . È importante notare che questo è semplicemente un contratto e non verrà applicato dal compilatore, se lo interrompi ottieni un comportamento indefinito e sei da solo. Se const è presente o no, non influirà sulla generazione del codice, almeno non rispetto alle gare di dati .

Ciò significa che const è ora l’equivalente di Java synchronized ?

No Affatto…

Considera la seguente class eccessivamente semplificata che rappresenta un rettangolo:

 class rect { int width = 0, height = 0; public: /*...*/ void set_size( int new_width, int new_height ) { width = new_width; height = new_height; } int area() const { return width * height; } }; 

L’ area funzioni membro è thread-safe ; non perché è const , ma perché consiste interamente di operazioni di lettura. Non ci sono scritture coinvolte e almeno una scrittura coinvolta è necessaria per il verificarsi di una corsa di dati . Ciò significa che puoi chiamare l’ area da tutti i thread che vuoi e otterrai sempre risultati corretti.

Nota che questo non significa che il rect sia sicuro per i thread . In effetti, è facile vedere come se una chiamata area dovesse accadere nello stesso momento in cui una chiamata a set_size su un dato rect , l’ area potrebbe finire per calcolare il risultato in base a una larghezza precedente e una nuova altezza (o addirittura su valori alterati).

Ma va bene, il rect non è const quindi non ci si aspetta nemmeno che sia sicuro per i thread dopo tutto. Un object dichiarato const rect , d’altra parte, sarebbe thread-safe poiché non sono possibili scritture (e se si considera const_cast -ing qualcosa originariamente dichiarato const si ottiene un comportamento indefinito e il gioco è fatto).

Quindi cosa significa allora?

Supponiamo, per amor di discussione, che le operazioni di moltiplicazione siano estremamente costose e che sia meglio evitarle quando ansible. Potremmo calcolare l’area solo se è richiesta, e poi salvarla nella cache in caso sia richiesta di nuovo in futuro:

 class rect { int width = 0, height = 0; mutable int cached_area = 0; mutable bool cached_area_valid = true; public: /*...*/ void set_size( int new_width, int new_height ) { cached_area_valid = ( width == new_width && height == new_height ); width = new_width; height = new_height; } int area() const { if( !cached_area_valid ) { cached_area = width; cached_area *= height; cached_area_valid = true; } return cached_area; } }; 

[Se questo esempio sembra troppo artificiale, potresti sostituire mentalmente int con un numero intero allocato dynamicmente molto grande che è intrinsecamente non thread-safe e per cui le moltiplicazioni sono estremamente costose.]

L’ area funzione membro non è più thread-safe , sta eseguendo le scritture ora e non è sincronizzata internamente. È un problema? La chiamata area può avvenire come parte di un costruttore di copia di un altro object, tale costruttore potrebbe essere stato chiamato da qualche operazione su un contenitore standard e, a quel punto, la libreria standard si aspetta che questa operazione si comporti come una lettura rispetto ai dati gare . Ma stiamo facendo delle scritture!

Non appena mettiamo un rect in un container standard, direttamente o indirettamente, stiamo entrando in un contratto con la Standard Library . Per continuare a scrivere in una funzione const continuando a rispettare questo contratto, è necessario sincronizzare internamente tali scritture:

 class rect { int width = 0, height = 0; mutable std::mutex cache_mutex; mutable int cached_area = 0; mutable bool cached_area_valid = true; public: /*...*/ void set_size( int new_width, int new_height ) { if( new_width != width || new_height != height ) { std::lock_guard< std::mutex > guard( cache_mutex ); cached_area_valid = false; } width = new_width; height = new_height; } int area() const { std::lock_guard< std::mutex > guard( cache_mutex ); if( !cached_area_valid ) { cached_area = width; cached_area *= height; cached_area_valid = true; } return cached_area; } }; 

Si noti che abbiamo reso la funzione area thread-safe , ma il rect non è ancora thread-safe . Una chiamata area avviene nello stesso momento in cui una chiamata a set_size può ancora finire per calcolare il valore sbagliato, dal momento che le assegnazioni di width e height non sono protette dal mutex.

Se volessimo davvero un rect sicuro per i thread, rect una primitiva di sincronizzazione per proteggere il rect non thread-safe .

Stanno finendo le parole chiave ?

Sì. Hanno finito le parole chiave fin dal primo giorno.


Fonte : non sai const e mutableHerb Sutter