Può acquisire carichi riordinare con altre operazioni di acquisizione? cppreference dice che solo non atomici e rilassati sono ordinati per acquisizione
Secondo C ++ Reference , mutex.lock()
è memory_order_acquire
un'operazione ed mutex.unlock()
è memory_order_release
un'operazione.
Tuttavia, memory_order_acquire
e memory_order_release
sono efficaci solo per operazioni atomiche non atomiche e rilassate.
memory_order: ordine di rilascio-acquisizione su cppreference
Se viene etichettato un negozio atomico nel thread A e viene etichettato
memory_order_release
un carico atomico nel thread B dalla stessa variabilememory_order_acquire
, tutta la memoria scrive (atomica non atomica e rilassata) che è avvenuta prima dell'archivio atomico dal punto di vista del thread A, diventano visibili effetti collaterali nel thread B
Il mutex in C ++ potrebbe garantire la visibilità delle operazioni atomiche? Un esempio è il seguente. Il codice può essere A
riordinato prima di mu.lock()
e il thread può essere b
letto x
come false
?
#include <thread>
#include <atomic>
#include <cassert>
#include <iostream>
#include <unistd.h>
std::atomic<bool> x = {false};
std::mutex mu;
void write_x(){
mu.lock();
std::cout << "write_x" << std::endl;
x.store(true, std::memory_order_release);
mu.unlock();
}
void read_x() {
mu.lock();
std::cout << "read_x" << std::endl;
assert(x.load(std::memory_order_acquire)); // A
mu.unlock();
}
int main() {
std::thread a(write_x);
usleep(1);
std::thread b(read_x);
a.join(); b.join();
return 0;
}
Risposte
TL: DR: "tutta la memoria scrive" significa tutto, non solo il tipo menzionato, ma il fraseggio è confuso. Probabilmente inteso solo per sottolineare che anche le operazioni atomiche non atomiche e rilassate sono visibili in modo sicuro attraverso un sincronizza con, ma nel fraseggio manca la parola "incluso".
Nota che cppreference è un wiki che intende spiegare lo standard. Non è un linguaggio tecnico normativo e talvolta spiega anche le cose in termini diversi rispetto allo standard ISO C ++.
In genere è molto buono, ma non dare per scontato che sia perfetto quando qualcosa sembra strano. Dal contesto circostante (e dalla sanità mentale), come l'ultima frase del paragrafo che dice "tutto" senza qualificazioni, è ancora abbastanza ovvio che questo sia ciò che si intendeva.
ISO C ++ è chiaro. Un'operazione di acquisizione che "vede" un'operazione di rilascio crea una relazione di sincronizzazione con. Tutto prima del rilascio è visibile al codice dopo l'operazione di acquisizione.
Quindi, in termini di un modello in cui le operazioni che accedono a uno stato di memoria condiviso coerente a livello globale, acquisiscono le operazioni bloccano tutto dal riordino prima di loro. Comprese le operazioni di rilascio e seq_cst. (Si noti che questa parte del cppreference non fa alcun riferimento al re ordinamento, solo per visibilità garantita o meno. Riordino locale di accessi alla stato coerente globale è, in pratica, come vera e propria CPU lavoro, quindi è spesso più conveniente per descrivere le cose in questo modo , come stai facendo nella domanda.)
Ciò significa che la definizione di acquisizione e rilascio di C ++ corrisponde alla terminologia standard senza eccezioni magiche folli. https://preshing.com/20120913/acquire-and-release-semantics/
Nota che alcune persone usano "atomiche rilassate" per descrivere tutti gli ordinamenti più deboli diseq_cst
. Esempio: Herb Sutter lo usa in questo modo nel discorso di cui tratta questa domanda .
Questo potrebbe essere ciò che si intendeva nella definizione di riferimento cpp, ma IDK perché vorrebbero escluderlo seq_cst
. Vengono ordinate tutte le operazioni atomiche e non atomiche. Quindi forse intendevano mo_relaxed
, e volevano solo sottolineare che anche quelli sono ordinati / visibili.
(si seq_cst
potrebbe dire che si ordina già da solo rispetto a tutto il resto , quindi "ovviamente" è ordinato rispetto alle operazioni di acquisizione e rilascio. Ma questo motivo sembra improbabile.)
Se era inteso per enfatizzare il fatto che anche gli ordini più deboli erano ordinati da esso, avrebbero dovuto scrivere " compreso l' atomico non atomico e rilassato" . Senza la parola "compreso", quella frase può essere letta come implicante solo non atomica e rilassata atomica. Solo una comprensione del quadro generale e di ciò che sarebbe sano o meno può darti una lettura corretta.
La scrittura tecnica che deve essere compresa con precisione userà spesso la frase "incluso ma non limitato a".
Nota inoltre che il tuo esempio può ancora attivare l'asserzione, ma non per il motivo per cui eri preoccupato.
Se l' a
avvio del thread è lento, il thread b
potrebbe entrare prima nella sua sezione critica e stampare + leggere x
prima che avvenga il print + store nell'altro thread.
Il modo usuale per scrivere esempi di giocattoli in questo modo è un ciclo che gira su un carico di acquisizione finché non vede un valore, ad esempio un flag come data_read
memorizzato da un'operazione di rilascio dopo il negozio a cui tieni. In questo modo sai che il lato di lettura viene eseguito dopo un'operazione di acquisizione sincronizzata con un'operazione di rilascio nel lato di scrittura.