C++C ++ 11 Modello di memoria


Osservazioni

Diversi thread che tentano di accedere alla stessa posizione di memoria partecipano a una corsa di dati se almeno una delle operazioni è una modifica (nota anche come operazione di memorizzazione ). Queste razze di dati causano un comportamento indefinito . Per evitarli è necessario impedire a questi thread di eseguire contemporaneamente operazioni in conflitto.

I primitivi di sincronizzazione (mutex, sezione critica e simili) possono proteggere tali accessi. Il modello di memoria introdotto in C ++ 11 definisce due nuovi modi portatili per sincronizzare l'accesso alla memoria in ambiente multi-thread: operazioni atomiche e recinzioni.

Operazioni atomiche

È ora possibile leggere e scrivere in una determinata posizione di memoria mediante l'utilizzo di operazioni di caricamento atomico e di immagazzinamento atomico . Per comodità questi sono inclusi nella classe template std::atomic<t> . Questa classe racchiude un valore di tipo t ma questa volta i carichi e i depositi sull'oggetto sono atomici.

Il modello non è disponibile per tutti i tipi. I tipi disponibili sono specifici dell'implementazione, ma di solito includono la maggior parte (o tutti) i tipi integrali disponibili e i tipi di puntatore. Quindi std::atomic<unsigned> e std::atomic<std::vector<foo> *> dovrebbero essere disponibili, mentre std::atomic<std::pair<bool,char>> molto probabilmente non lo sarà.

Le operazioni atomiche hanno le seguenti proprietà:

  • Tutte le operazioni atomiche possono essere eseguite simultaneamente da più thread senza causare un comportamento indefinito.
  • Un carico atomico vedrà sia il valore iniziale con cui è stato costruito l'oggetto atomico, sia il valore scritto su di esso tramite un'operazione di immagazzinamento atomico .
  • Le memorie atomiche sullo stesso oggetto atomico vengono ordinate allo stesso modo in tutti i thread. Se un thread ha già visto il valore di un'operazione dell'archivio atomico , le successive operazioni di caricamento atomico vedranno lo stesso valore o il valore memorizzato dall'operazione successiva dell'archivio atomico .
  • Le operazioni atomiche di lettura-modifica-scrittura consentono al carico atomico e all'archivio atomico di accadere senza altri depositi atomici nel mezzo. Ad esempio si può incrementare atomicamente un contatore da più thread e nessun incremento andrà perso indipendentemente dalla contesa tra i thread.
  • Le operazioni atomiche ricevono un parametro facoltativo std::memory_order che definisce quali proprietà aggiuntive l'operazione ha rispetto ad altre posizioni di memoria.
std :: memory_order Senso
std::memory_order_relaxed senza ulteriori restrizioni
std::memory_order_releasestd::memory_order_acquire se load-acquire vede il valore memorizzato da store-release quindi memorizza il sequenziamento prima che lo store-release avvenga prima che i carichi siano sequenziati dopo che il carico acquisisce
std::memory_order_consume come memory_order_acquire ma solo per carichi dipendenti
std::memory_order_acq_rel combina load-acquire e store-release
std::memory_order_seq_cst consistenza sequenziale

Questi tag di ordine di memoria consentono tre diverse discipline di ordinamento della memoria: coerenza sequenziale , rilassato e acquisizione-acquisizione con il suo rilascio-consumo di fratello.

Consistenza sequenziale

Se non viene specificato un ordine di memoria per un'operazione atomica, l'ordine assume come impostazione predefinita la coerenza sequenziale . Questa modalità può anche essere selezionata in modo esplicito taggando l'operazione con std::memory_order_seq_cst .

Con questo ordine nessuna operazione di memoria può attraversare l'operazione atomica. Tutte le operazioni di memoria sequenziate prima dell'operazione atomica avvengono prima dell'operazione atomica e l'operazione atomica avviene prima di tutte le operazioni di memoria che vengono sequenziate dopo di essa. Questa modalità è probabilmente la più facile da ragionare, ma porta anche alla peggiore penalizzazione delle prestazioni. Inoltre impedisce tutte le ottimizzazioni del compilatore che potrebbero altrimenti tentare di riordinare le operazioni dopo l'operazione atomica.

Ordinamento rilassato

L'opposto alla coerenza sequenziale è l'ordine di memoria rilassato . È selezionato con il tag std::memory_order_relaxed . L'operazione atomica rilassata non imporrà restrizioni sulle altre operazioni di memoria. L'unico effetto che rimane è che l'operazione è di per sé ancora atomica.

Rilascio-Acquisisci ordini

Un'operazione di archivio atomico può essere contrassegnata con std::memory_order_release e un'operazione di caricamento atomico può essere contrassegnata con std::memory_order_acquire . La prima operazione è chiamata (atomica) store-release mentre la seconda è chiamata (atomic) load-acquire .

Quando load-acquire vede il valore scritto da un store-release accade quanto segue: tutte le operazioni di memorizzazione sequenziate prima che lo store-release diventino visibili a ( accadono prima ) le operazioni di caricamento che sono sequenziate dopo l' acquisizione del carico .

Le operazioni atomiche di lettura-modifica-scrittura possono anche ricevere il tag cumulativo std::memory_order_acq_rel . Questo rende la porzione carico atomica dell'operazione un atomico carico acquisiscono mentre la porzione negozio atomico diventa deposito release atomico.

Il compilatore non può spostare le operazioni del negozio dopo un'operazione di rilascio dello store atomico . Inoltre, non è consentito spostare le operazioni di carico prima che il carico atomico acquisisca (o carichi-consumi ).

Si noti inoltre che non esiste il rilascio del carico atomico o l'acquisizione del negozio atomico . Il tentativo di creare tali operazioni li rende operazioni rilassate .

Rilascio-Consuma ordine

Questa combinazione è simile a release-acquire , ma questa volta il carico atomico viene etichettato con std::memory_order_consume e diventa (atomico) carico-consumo . Questa modalità è la stessa di Release-Acquisisci con la sola differenza che tra le operazioni di caricamento in sequenza dopo il carico-consumano solo queste, in base al valore caricato dal carico-consumo, vengono ordinate.

Recinzioni

Le fence consentono inoltre di ordinare le operazioni di memoria tra i thread. Una recinzione è una barriera di rilascio o un recinto.

Se un recinto di rilascio si verifica prima di un recinto di acquisizione, memorizza i sequenziati prima che il recinto di rilascio sia visibile ai carichi sequenziati dopo il recinto di acquisizione. Per garantire che il recinto di rilascio avvenga prima del recinto di acquisizione, si possono usare altre primitive di sincronizzazione che includono operazioni atomiche rilassate.

C ++ 11 Modello di memoria Esempi correlati