2013-04-23 18 views
21

http://en.cppreference.com/w/cpp/atomic/memory_order, e altri C++ 11 riferimenti online, definiscono memory_order_acquire e memory_order_release come: operazioneC++ 11 memory_order_acquire e memory_order_release semantica?

  • Acquire: no legge nel thread corrente eventuale ricambio prima di questo carico.
  • Operazione di rilascio: no Scrive nel thread corrente può essere riordinato dopo questo negozio.

Questo sembra consentire post-acquisire-scrive da eseguire prima di l'operazione acquisiscono, che sembra strano (movimento soliti acquisire/rilascio semantica funzionamento limito di tutte operazioni di memoria) anche me.

Stesso fonte online (http://en.cppreference.com/w/cpp/atomic/atomic_flag) suggerisce che un mutex spinlock può essere costruito utilizzando Atomics C++ e il sopra citato rilassato regole di ordinamento della memoria: non

lock mutex: while (lock.test_and_set(std::memory_order_acquire)) 

unlock mutex: lock.clear(std::memory_order_release);    

Con questa definizione di blocco/sblocco, sarebbe il semplice codice sotto essere rotto se memory_order_acquire/release sono infatti così definito (cioè, non vietando riordino di post-acquisire-scrive):

Thread1: 
    (0) lock 
    (1) x = 1; 
    (2) if (x != 1) PANIC 
    (3) unlock 

Thread2: 
    (4) lock 
    (5) x = 0; 
    (6) unlock 

è la seguente esecuzione possibile: (0) serratura (1) x = 1, (5) x = 0, (2) P ANIC? Cosa mi sono perso?

+0

Come pensi sia possibile? Qual è l'ordine preciso degli eventi (comprese le serrature e gli sblocchi) che immagini? –

+1

Ho aggiunto il blocco nella traccia sopra. Immagino che il post-acquisizione-scrittura a (5) possa essere eseguito prima (4). –

risposta

20

L'implementazione di spinex mutex mi sembra a posto. Penso che abbiano le definizioni di acquisire e versione completamente errate.

Ecco la spiegazione più chiara dei modelli di coerenza di acquisizione/rilascio di cui sono a conoscenza: Gharachorloo; Lenoski; Laudon; Gibbons; Gupta; Hennessy: Memory consistency and event ordering in scalable shared-memory multiprocessors, Int'l Symp Comp Arch, ISCA(17):15-26, 1990, doi 10.1145/325096.325102. (Il DOI è dietro il paywall ACM il collegamento reale è quello di una copia non dietro un paywall..)

Guarda Condizione 3.1 nella sezione 3.3 e allegata figura 3:

  • prima di un ordinario carico o accesso deposito è consentito effettuare rispetto a qualsiasi altro processore, tutti precedenti accessi acquisiscono devono essere eseguite e
  • prima di un accesso rilascio può eseguire con rispetto a qualsiasi altro processore, tutti i precedenti ordinaria carico un d Gli accessi al negozio devono essere eseguiti, e
  • gli accessi speciali sono [sequenzialmente] coerenti con lo l'uno rispetto all'altro.

Il punto è questo: acquisisce e stampa sono sequenzialmente coerenti (. Tutte le discussioni concordano globalmente all'ordine in cui acquisisce e rilascia accaduto) Tutti i thread globalmente concordano che la roba che avviene tra un'acquisizione e un rilascio su un thread specifico è successo tra l'acquisizione e il rilascio.Ma carichi e negozi normali dopo una versione può essere spostata (sia dall'hardware o dal compilatore) sopra il rilascio, sia carichi normali e memorizza prima dello un'acquisizione può essere spostata (dall'hardware o dal compilatore) dopo l'acquisizione.

Nel C++ standard (ho utilizzato il collegamento alla bozza di gennaio 2012) la sezione pertinente è 1.10 (pagine da 11 a 14).

La definizione di avviene prima dello deve essere modellata dopo Lamport; Time, Clocks, and the Ordering of Events in a Distributed System, CACM, 21(7):558-565, Jul 1978. C++ acquisisce corrispondono alle Lamport di riceve, C++ rilasci corrispondere al di Lamport manda. Lamport ha inserito un ordine totale sulla sequenza di eventi all'interno di un singolo thread, in cui C++ deve consentire un ordine parziale (vedere Sezione 1.9, Paragrafi 13-15, pagina 10 per la definizione C++ di in sequenza, prima di). sequenziato prima dell'ordinazione è praticamente ciò che ti aspetteresti. Le istruzioni sono sequenziate nell'ordine in cui sono fornite nel programma. Sezione 1.9, paragrafo 14: "Ogni computazione del valore ed e ff etto laterale associati a un'espressione completa viene sequenziato prima di ogni calcolo del valore associato alla successiva espressione completa da valutare."

Il punto di sezione 1.10 è dire che un programma che è-connessione dati-corsa produce lo stesso valore ben definito come se il programma sono stati eseguiti su un computer con una sequenza memoria coerente e nessun compilatore riordino. Se c'è una corsa di dati, allora il programma non ha alcuna semantica definita. Se non vi è alcuna corsa di dati, il compilatore (o la macchina) è autorizzato a riordinare le operazioni che non contribuiscono all'illusione della coerenza sequenziale.

Sezione 1.10, paragrafo 21 (pagina 14) dice: Un programma non è dati senza gara se v'è una coppia di accessi A e B da diversi thread per oggetto X, almeno uno di questi accessi ha un effetto collaterale, e né A accade - prima di B, né B succede - prima di A. Altrimenti il ​​programma è privo di dati.

I paragrafi 6-20 forniscono una definizione molto attenta della relazione prima-succede. La definizione chiave è Paragrafo 12:

"Una valutazione Un avviene prima una valutazione B se:

  • A è sequenziato prima B, o
  • Un inter-thread succede prima B."

Quindi, se un acquisiscono viene sequenziato prima (nello stesso thread) praticamente qualsiasi altra dichiarazione, poi l'acquisiscono deve apparire per accadere prima di questa affermazione. (Compreso se quell'istruzione esegue una scrittura.)

Allo stesso modo: se una qualsiasi affermazione è sequenziata prima di (nella stessa thread) una release, allora quella dichiarazione deve apparire prima della release. (Compreso se tale istruzione esegue solo un calcolo del valore (leggi).)

La ragione per cui il compilatore è permesso di spostare altri calcoli da dopo un rilascio per prima un rilascio (o da prima un'acquisizione a dopo un'acquisizione) è a causa del fatto che tali operazioni specificamente Non avere un inter-thread avviene prima della relazione (perché sono al di fuori della sezione critica). Se corrono la semantica non sono definiti, e se non corrono (perché non sono condivisi), allora non puoi dire esattamente quando sono accaduti riguardo alla sincronizzazione.

Il che è un modo molto lungo per dire: le definizioni di acquisizione e rilascio di cppreference.com sono completamente sbagliate. Il tuo programma di esempio non ha alcuna condizione di race dei dati e PANIC non può verificarsi.

+0

Grazie. Ho familiarità con la carta - questa è la fonte della mia confusione: acquisire semantica di accesso/operazione di solito garantisce che sia i carichi che i negozi che seguono (in ordine di programma) appaiono effettivamente come eseguiti dopo l'operazione di acquisizione. cppreference tuttavia sembra consentire che le scritture appaiano come eseguite prima. –

+0

Non sono ancora del tutto convinto. Le modifiche a 'x' non sono ad un oggetto atomico, e quindi non sono" effetti collaterali visibili ". Di conseguenza, penso che un compilatore possa riordinare o rimuovere operazioni su x a causa della "regola as-if" 1.9/1. (Anche se in questo particolare esempio è più probabile che il compilatore ottimizzi x e la chiamata a PANIC interamente dato che la costante propagazione di x nel test 'if' consente la rimozione della chiamata' PANIC' interamente e lo store nella thread 2 –

+0

Le modifiche a 'x' sono effetti collaterali visibili È solo una questione di _quando_ gli effetti collaterali diventano visibili ad altri thread.Penso a questo come: le operazioni di rilascio" trasmettono "la visibilità del lato non atomico- effetti su altri thread. Acquisire le operazioni "ricevere" la visibilità di quegli effetti collaterali non atomici. Quindi, quando Thread 1 di CedomirSegulja esegue (0) bloccarlo ora deve credere che Thread 2 non abbia ancora raggiunto (4) o che Thread 2 è passato (6). –