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.
Come pensi sia possibile? Qual è l'ordine preciso degli eventi (comprese le serrature e gli sblocchi) che immagini? –
Ho aggiunto il blocco nella traccia sopra. Immagino che il post-acquisizione-scrittura a (5) possa essere eseguito prima (4). –