Come raggiungere una barriera StoreLoad in C ++ 11?


13

Voglio scrivere un codice portatile (Intel, ARM, PowerPC ...) che risolva una variante di un problema classico:

Initially: X=Y=0

Thread A:
  X=1
  if(!Y){ do something }
Thread B:
  Y=1
  if(!X){ do something }

in cui l'obiettivo è quello di evitare una situazione in cui entrambi i thread stanno facendosomething . (Va bene se nessuna delle due cose funziona; questo non è un meccanismo run-esattamente-una volta.) Correggimi se vedi alcuni difetti nel mio ragionamento di seguito.

Sono consapevole che posso raggiungere l'obiettivo con le s memory_order_seq_cstatomiche come segue:storeload

std::atomic<int> x{0},y{0};
void thread_a(){
  x.store(1);
  if(!y.load()) foo();
}
void thread_b(){
  y.store(1);
  if(!x.load()) bar();
}

che raggiunge l'obiettivo, perché ci deve essere un singolo ordine totale sugli
{x.store(1), y.store(1), y.load(), x.load()}eventi, che deve concordare con gli "spigoli" dell'ordine del programma:

  • x.store(1) "in TO è prima" y.load()
  • y.store(1) "in TO è prima" x.load()

e se è foo()stato chiamato, allora abbiamo un vantaggio aggiuntivo:

  • y.load() "legge il valore prima" y.store(1)

e se è bar()stato chiamato, allora abbiamo un vantaggio aggiuntivo:

  • x.load() "legge il valore prima" x.store(1)

e tutti questi bordi combinati insieme formerebbero un ciclo:

x.store(1)"in TO è prima" y.load()"legge il valore prima" y.store(1)"in TO è prima" x.load()"legge il valore prima"x.store(true)

il che viola il fatto che gli ordini non hanno cicli.

Uso intenzionalmente termini non standard "in TO is before" e "legge valore prima" in contrapposizione a termini standard come happens-before, perché desidero sollecitare un feedback sulla correttezza della mia ipotesi che questi limiti implicino effettivamente una happens-beforerelazione, che possono essere combinati insieme in un singolo grafico e il ciclo in tale grafico combinato è vietato. Non ne sono sicuro. Quello che so è che questo codice produce barriere corrette su Intel gcc & clang e su ARM gcc


Ora, il mio vero problema è un po 'più complicato, perché non ho alcun controllo su "X" - è nascosto dietro alcune macro, modelli ecc. E potrebbe essere più debole di seq_cst

Non so nemmeno se "X" è una singola variabile, o qualche altro concetto (ad esempio un semaforo leggero o mutex). Tutto quello che so è che ho due macro set()e check()tale che check()ritorna true"dopo" un altro thread chiamato set(). (Si è anche noto che sete checksono thread-safe e non può creare UB dati-gara.)

Quindi concettualmente set()è un po 'come "X = 1" ed check()è come "X", ma non ho alcun accesso diretto all'atomica, se presente.

void thread_a(){
  set();
  if(!y.load()) foo();
}
void thread_b(){
  y.store(1);
  if(!check()) bar();
}

Sono preoccupato, set()potrebbe essere implementato internamente come x.store(1,std::memory_order_release)e / o check()potrebbe essere x.load(std::memory_order_acquire). O ipoteticamente, un std::mutexthread si sta sbloccando e un altro è try_locking; nello standard ISO std::mutexè garantito solo l'acquisto e il rilascio degli ordini, non seq_cst.

Se questo è il caso, allora check()se il corpo può essere "riordinato" prima y.store(true)( vedi la risposta di Alex dove dimostrano che ciò accade su PowerPC ).
Questo sarebbe davvero brutto, poiché ora questa sequenza di eventi è possibile:

  • thread_b()prima carica il vecchio valore di x( 0)
  • thread_a() esegue tutto compreso foo()
  • thread_b() esegue tutto compreso bar()

Quindi, entrambi foo()e bar()sono stato chiamato, cosa che ho dovuto evitare. Quali sono le mie opzioni per impedirlo?


Opzione A

Prova a forzare la barriera Store-Load. Questo, in pratica, può essere raggiunto da std::atomic_thread_fence(std::memory_order_seq_cst);- come spiegato da Alex in una risposta diversa, tutti i compilatori testati hanno emesso un recinto completo:

  • x86_64: MFENCE
  • PowerPC: hwsync
  • Itanuim: mf
  • ARMv7 / ARMv8: dmb ish
  • MIPS64: sincronizzazione

Il problema con questo approccio è che non ho trovato alcuna garanzia nelle regole C ++, che std::atomic_thread_fence(std::memory_order_seq_cst)deve tradursi in una barriera di memoria piena. In realtà, il concetto di atomic_thread_fences in C ++ sembra avere un diverso livello di astrazione rispetto al concetto di assemblaggio di barriere di memoria e si occupa più di cose come "quale operazione atomica si sincronizza con cosa". Esistono prove teoriche del fatto che la realizzazione di seguito raggiunge l'obiettivo?

void thread_a(){
  set();
  std::atomic_thread_fence(std::memory_order_seq_cst)
  if(!y.load()) foo();
}
void thread_b(){
  y.store(true);
  std::atomic_thread_fence(std::memory_order_seq_cst)
  if(!check()) bar();
}

Opzione B

Usa il controllo che abbiamo su Y per ottenere la sincronizzazione, usando le operazioni di lettura-modifica-scrittura memory_order_acq_rel su Y:

void thread_a(){
  set();
  if(!y.fetch_add(0,std::memory_order_acq_rel)) foo();
}
void thread_b(){
  y.exchange(1,std::memory_order_acq_rel);
  if(!check()) bar();
}

L'idea qui è che gli accessi a un singolo atomico ( y) debbano essere costituiti da un unico ordine su cui tutti gli osservatori concordano, quindi o fetch_addè prima exchangeo viceversa.

Se fetch_addè prima di exchangeallora la parte "release" di si fetch_addsincronizza con la parte "acquisisci" di exchangee quindi tutti gli effetti collaterali di set()devono essere visibili all'esecuzione del codice check(), quindi bar()non saranno chiamati.

Altrimenti, exchangeè prima fetch_add, quindi fetch_addvedrà 1e non chiamerà foo(). Quindi, è impossibile chiamare sia foo()e bar(). Questo ragionamento è corretto?


Opzione C

Usa atomici fittizi per introdurre "bordi" che impediscono il disastro. Prendi in considerazione il seguente approccio:

void thread_a(){
  std::atomic<int> dummy1{};
  set();
  dummy1.store(13);
  if(!y.load()) foo();
}
void thread_b(){
  std::atomic<int> dummy2{};
  y.store(1);
  dummy2.load();
  if(!check()) bar();
}

Se pensi che il problema qui sia atomics locale, allora immagina di spostarli in ambito globale, nel seguente ragionamento non mi sembra importante, e ho intenzionalmente scritto il codice in modo tale da esporre quanto sia divertente quel manichino1 e dummy2 sono completamente separati.

Perché sulla Terra questo potrebbe funzionare? Bene, ci deve essere un singolo ordine totale di {dummy1.store(13), y.load(), y.store(1), dummy2.load()}cui deve essere coerente con gli "spigoli" dell'ordine del programma:

  • dummy1.store(13) "in TO è prima" y.load()
  • y.store(1) "in TO è prima" dummy2.load()

(Seq_cst store + load si spera formino l'equivalente in C ++ di una barriera di memoria completa incluso StoreLoad, come fanno in caso di ISA reali incluso AArch64 dove non sono richieste istruzioni di barriera separate.)

Ora, abbiamo due casi da considerare: o y.store(1)è prima y.load()o dopo nell'ordine totale.

Se y.store(1)è prima, y.load()allora foo()non verrà chiamato e siamo al sicuro.

Se y.load()è prima y.store(1), quindi combinandolo con i due bordi che abbiamo già nell'ordine del programma, deduciamo che:

  • dummy1.store(13) "in TO è prima" dummy2.load()

Ora, si dummy1.store(13)tratta di un'operazione di rilascio, che rilascia effetti di set(), ed dummy2.load()è un'operazione di acquisizione, quindi check()dovremmo vedere gli effetti di set()e quindi bar()non saranno chiamati e siamo al sicuro.

È corretto qui pensare che check()vedrà i risultati di set()? Posso combinare i "bordi" di vario tipo ("ordine di programma", noto anche come Sequenced Before, "ordine totale", "prima del rilascio", "dopo l'acquisizione") in questo modo? Ne dubito seriamente: le regole del C ++ sembrano parlare di relazioni "sincronizzate con" tra negozio e carico nella stessa posizione - qui non esiste una situazione del genere.

Tieni presente che siamo solo preoccupati per il caso in cui dumm1.storeè noto (tramite altri ragionamenti) prima dummy2.loadnell'ordine totale seq_cst. Quindi se avessero avuto accesso alla stessa variabile, il carico avrebbe visto il valore memorizzato e si sarebbe sincronizzato con esso.

(Il ragionamento della barriera di memoria / riordino delle implementazioni in cui i carichi atomici e gli archivi si compilano in almeno barriere di memoria a 1 via (e le operazioni seq_cst non possono essere riordinate: ad esempio un archivio seq_cst non può passare un carico seq_cst) è che qualsiasi carico / i negozi dopo dummy2.loaddiventano sicuramente visibili agli altri thread dopo y.store . E allo stesso modo per l'altro thread, ... prima y.load.)


Puoi giocare con la mia implementazione delle Opzioni A, B, C su https://godbolt.org/z/u3dTa8


1
Il modello di memoria C ++ non ha alcun concetto di riordino StoreLoad, si sincronizza solo con e succede prima. (E UB su corse di dati su oggetti non atomici, a differenza di asm per hardware reale.) Su tutte le implementazioni reali di cui sono a conoscenza, std::atomic_thread_fence(std::memory_order_seq_cst)si compila in una barriera completa, ma poiché l'intero concetto è un dettaglio di implementazione che non troverai qualsiasi menzione nello standard. (Modelli di memoria CPU solito sono definiti in termini di ciò reorerings è consentito rispetto alla consistenza sequenziale es x86 è seq-cst + tampone negozio w / inoltro.)
Peter Cordes

@PeterCordes grazie, potrei non essere stato chiaro nei miei scritti. Volevo comunicare ciò che hai scritto nella sezione "Opzione A". So che il titolo della mia domanda usa la parola "StoreLoad" e che "StoreLoad" è un concetto proveniente da un mondo completamente diverso. Il mio problema è come mappare questo concetto in C ++. O se non può essere mappato direttamente, allora come raggiungere l'obiettivo che mi sono posto: prevenire foo()e bar()essere entrambi chiamati.
qbolec,

1
È possibile utilizzare compare_exchange_*per eseguire un'operazione RMW su un bool atomico senza modificarne il valore (basta impostare lo stesso valore atteso e nuovo).
mpoeter

1
@Fareanor e qbolec: atomic<bool>ha exchangee compare_exchange_weak. Quest'ultimo può essere usato per fare un RMW fittizio (tentando di) CAS (vero, vero) o falso, falso. Non riesce o sostituisce atomicamente il valore con se stesso. (In x86-64 asm, il trucco lock cmpxchg16bè come si fa a garantire carichi atomici garantiti a 16 byte; inefficiente ma meno male che prendere un blocco separato.)
Peter Cordes

1
@PeterCordes sì lo so che può succedere che né foo()bar()sarà chiamato. Non volevo portare a molti elementi "reali" del codice, per evitare "pensi di avere il problema X ma hai il problema Y" tipo di risposte. Ma, se uno ha davvero bisogno di sapere qual è il piano di fondo: set()è davvero some_mutex_exit(), check()è try_enter_some_mutex(), yè "ci sono alcuni camerieri", foo()è "uscita senza svegliare nessuno", bar()è "aspetta il risveglio" ... Ma, mi rifiuto di discuti qui di questo disegno - non posso davvero cambiarlo.
qbolec,

Risposte:


5

Le opzioni A e B sono soluzioni valide.

  • Opzione A: non importa in cosa si traduca una recinzione seq-cst, lo standard C ++ definisce chiaramente quali garanzie fornisce. Le ho presentate in questo post: quando è utile un recinto memory_order_seq_cst?
  • Opzione B: sì, il tuo ragionamento è corretto. Tutte le modifiche su alcuni oggetti hanno un unico ordine totale (l'ordine di modifica), quindi puoi usarlo per sincronizzare i thread e garantire la visibilità di tutti gli effetti collaterali.

Tuttavia, l'opzione C non è valida! Una relazione di sincronizzazione con può essere stabilita solo mediante operazioni di acquisizione / rilascio sullo stesso oggetto . Nel tuo caso si dispone di due oggetti completamente diversi e indepent dummy1e dummy2. Ma questi non possono essere usati per stabilire una relazione prima. Infatti, poiché le variabili atomiche sono puramente locali (cioè, vengono toccate sempre e solo da un thread), il compilatore è libero di rimuoverle in base alla regola as-if .

Aggiornare

Opzione A:
presumo set()e check()opero su un valore atomico. Quindi abbiamo la seguente situazione (-> indica sequenziato prima ):

  • set()-> fence1(seq_cst)->y.load()
  • y.store(true)-> fence2(seq_cst)->check()

Quindi possiamo applicare la seguente regola:

Per le operazioni atomiche A e B su un oggetto atomico M , dove A modifica M e B assume il suo valore, se ci sono memory_order_seq_cstrecinzioni X e Y tali che A è sequenziato prima di X , Y è sequenziato prima di B e X precede Y in S , quindi B osserva gli effetti di A o una successiva modifica di M nel suo ordine di modifica.

Vale a dire, o check()vede quel valore archiviato seto y.load()vede il valore scritto y.store()(le operazioni su ypossono persino usare memory_order_relaxed).

Opzione C:
lo standard C ++ 17 afferma [32.4.3, p1347]:

Ci sarà un unico ordine totale S su tutte le memory_order_seq_cstoperazioni, coerente con l'ordine "succede prima" e gli ordini di modifica per tutte le località interessate [...]

La parola importante qui è "coerente". Ciò implica che se un'operazione Un accade-prima di un'operazione B , allora A deve precedere B in S . Tuttavia, implicazione logica è un one-way-strada, quindi non possiamo inferire l'inverso: solo perché alcuni di funzionamento C precede un'operazione D a S non implica che C succede prima D .

In particolare, non è possibile utilizzare due operazioni seq-cst su due oggetti separati per stabilire una relazione che si verifica prima, anche se le operazioni sono totalmente ordinate in S. Se si desidera ordinare operazioni su oggetti separati, è necessario fare riferimento a seq-cst -fences (vedi Opzione A).


Non è ovvio che l'opzione C non sia valida. operazioni seq-cst anche su oggetti privati ​​possono ancora ordinare altre operazioni in una certa misura. D'accordo, non c'è sincronizzazione, ma a noi non importa quale dei passaggi di foo o bar (o apparentemente nessuno dei due), solo che non funzionano entrambi . Penso che la relazione sequenziata prima e l'ordine totale delle operazioni seq-cst (che devono esistere) ci diano.
Peter Cordes,

Grazie @mpoeter. Potresti per favore approfondire l'opzione A. Quale dei tre proiettili nella tua risposta si applica qui? IIUC se y.load()non vede l'effetto di y.store(1), allora possiamo dimostrare dalle regole che in S, atomic_thread_fencedi thread_a è prima atomic_thread_fencedi thread_b. Quello che non vedo è come arrivare da questo alla conclusione a cui set()sono visibili gli effetti collaterali check().
qbolec,

1
@qbolec: ho aggiornato la mia risposta con maggiori dettagli sull'opzione A.
mpoeter

1
Sì, un'operazione seq-cst locale farebbe comunque parte del singolo ordine totale S su tutte le operazioni seq-cst. Ma S è "solo" coerente con l'accade-prima di ordine e modifica ordini , vale a dire, se una accade-prima B , allora A deve precedere B in S . Ma l'inverso non è garantito, vale a dire, solo perché A precede B in S , che non può dedurre , che una accade-prima B .
mpoeter

1
Bene, supponendo che sete checkpossa essere tranquillamente eseguito in parallelo, probabilmente andrei con l'opzione A, soprattutto se questo è critico per le prestazioni, poiché evita la contesa sulla variabile condivisa y.
mpoeter

1

Nel primo esempio, la y.load()lettura di 0 non implica che ciò y.load()accada prima y.store(1).

Ciò implica tuttavia che è precedente nel singolo ordine totale grazie alla regola che un carico seq_cst restituisce il valore dell'ultimo negozio seq_cst nell'ordine totale o il valore di alcuni negozi non seq_cst che non si verificano prima esso (che in questo caso non esiste). Quindi, se y.store(1)fosse prima rispetto y.load()all'ordine totale, y.load()avrebbe restituito 1.

La prova è ancora corretta perché il singolo ordine totale non ha un ciclo.

Che ne dici di questa soluzione?

std::atomic<int> x2{0},y{0};

void thread_a(){
  set();
  x2.store(1);
  if(!y.load()) foo();
}

void thread_b(){
  y.store(1);
  if(!x2.load()) bar();
}

Il problema dell'OP è che non ho alcun controllo su "X" - è dietro le macro del wrapper o qualcosa del genere e potrebbe non essere un archivio / caricamento seq-cst. Ho aggiornato la domanda per evidenziarlo meglio.
Peter Cordes,

@PeterCordes L'idea era di creare un'altra "x" su cui ha il controllo. Lo rinominerò in "x2" nella mia risposta per renderlo più chiaro. Sono sicuro che mi manchi qualche requisito, ma se l'unico requisito è assicurarsi che foo () e bar () non siano entrambi chiamati, allora questo soddisfa.
Tomek Czajka,

Lo farei if(false) foo();però , ma penso che neanche l'OP voglia questo: P Punto interessante ma penso che l'OP voglia che le chiamate condizionate siano basate sulle condizioni che specificano!
Peter Cordes,

1
Ciao @TomekCzajka, grazie per aver dedicato del tempo a proporre una nuova soluzione. Non funzionerebbe nel mio caso particolare, poiché omette importanti effetti collaterali di check()(vedi il mio commento alla mia domanda per il significato del mondo reale di set,check,foo,bar). Penso che potrebbe funzionare if(!x2.load()){ if(check())x2.store(0); else bar(); }invece.
qbolec

1

@mpoeter ha spiegato perché le opzioni A e B sono sicure.

In pratica su implementazioni reali, penso che l'opzione A abbia bisogno solo std::atomic_thread_fence(std::memory_order_seq_cst)nel thread A, non in B.

gli archivi seq-cst in pratica includono una barriera di memoria piena, o su AArch64 almeno non possono riordinare con carichi successivi di acquisizione o seq_cst (il stlrrilascio sequenziale deve scaricare dal buffer dell'archivio prima di ldarpoter leggere dalla cache).

Le mappature C ++ -> asm hanno la possibilità di mettere il costo dello svuotamento del buffer di memoria su depositi atomici o carichi atomici. La scelta sana per le implementazioni reali è quella di rendere economici i carichi atomici, quindi i negozi seq_cst includono una barriera completa (incluso StoreLoad). Mentre seq_cst i carichi sono gli stessi di acquisire carichi sulla maggior parte.

(Ma non POWER; ci sono anche carichi che richiedono una sincronizzazione pesante = barriera completa per fermare lo store forwarding da altri thread SMT sullo stesso core che potrebbe portare al riordino di IRIW, perché seq_cst richiede che tutti i thread siano in grado di concordare l'ordine di tutte le operazioni seq_cst. Due scritture atomiche in posizioni diverse in thread diversi verranno sempre visualizzate nello stesso ordine da altri thread? )

(Naturalmente per una garanzia formale di sicurezza, abbiamo bisogno sia di una barriera sia per promuovere il set di acquisizione / rilascio () -> check () in un seq_cst sincronizzato con. Funzionerebbe anche per un set rilassato, penso, ma un controllo rilassato potrebbe riordinare con barra dal POV di altri thread.)


Penso che il vero problema con l'opzione C sia che dipende da un ipotetico osservatore che potrebbe sincronizzarsi con yle operazioni fittizie. E quindi ci aspettiamo che il compilatore mantenga tale ordinamento quando si richiede un ISA basato su barriere.

Questo sarà vero in pratica su veri ISA; entrambi i thread includono una barriera completa o equivalente e i compilatori non ottimizzano (ancora) l'atomica. Ma ovviamente la "compilazione in un ISA basato su barriere" non fa parte dello standard ISO C ++. La cache condivisa coerente è l'osservatore ipotetico che esiste per il ragionamento asm ma non per il ragionamento ISO C ++.

Perché l'opzione C funzioni, abbiamo bisogno di un ordine come dummy1.store(13);/ y.load()/ set();(come visto dal thread B) per violare alcune regole ISO C ++ .

Il thread che esegue queste istruzioni deve comportarsi come se fosse set() eseguito per primo (a causa di Sequenced Before). Va bene, l'ordinamento della memoria di runtime e / o il riordino delle operazioni in fase di compilazione potrebbero ancora farlo.

I due ops seq_cst d1=13e ysono coerenti con la Sequenced Prima (ordine del programma). set()non partecipa all'ordine globale necessario per esistere per seq_cst ops perché non è seq_cst.

Filo B non sincronizza-con dummy1.store quindi non accade-prima requisito setrelativo ad d1=13applica , anche se tale assegnazione è un'operazione di rilascio.

Non vedo altre possibili violazioni delle regole; Non riesco a trovare nulla che sia necessario per essere coerente con il setSequenced-Before d1=13.

Il ragionamento "dummy1.store rilascia set ()" è il difetto. Tale ordinamento vale solo per un vero osservatore che si sincronizza con esso, o in asm. Come ha risposto @mpoeter, l'esistenza dell'ordine totale seq_cst non crea o implica relazioni che accadono prima, e questa è l'unica cosa che garantisce formalmente l'ordine al di fuori di seq_cst.

Qualsiasi tipo di CPU "normale" con cache condivisa coerente in cui questo riordino potrebbe realmente avvenire in fase di esecuzione non sembra plausibile. (Ma se un compilatore potesse rimuovere dummy1e dummy2quindi chiaramente avremmo un problema, e penso che sia consentito dallo standard.)

Ma poiché il modello di memoria C ++ non è definito in termini di buffer di archivio, cache coerente condivisa o test di tornasole del riordino consentito, le cose richieste dalla sanità mentale non sono formalmente richieste dalle regole C ++. Questo è forse intenzionale per consentire l'ottimizzazione anche delle variabili seq_cst che risultano essere thread private. (I compilatori attuali non lo fanno, ovviamente, o qualsiasi altra ottimizzazione degli oggetti atomici.)

Un'implementazione in cui un thread potrebbe davvero vedere per set()ultimo mentre un altro potrebbe vedere i set()primi suoni non plausibili. Nemmeno POWER potrebbe farlo; sia seq_cst load che store contengono barriere complete per POWER. (Avevo suggerito nei commenti che il riordino di IRIW potrebbe essere rilevante qui; le regole acq / rel di C ++ sono abbastanza deboli da adattarlo, ma la totale mancanza di garanzie al di fuori delle situazioni di sincronizzazione o di altro tipo prima che sia molto più debole di qualsiasi HW. )

C ++ non garantisce nulla per non seq_cst meno che in realtà non v'è un osservatore, e solo per quella dell'osservatore. Senza uno siamo nel territorio dei gatti di Schroedinger. Oppure, se due alberi cadono nella foresta, uno è caduto prima dell'altro? (Se è una grande foresta, la relatività generale dice che dipende dall'osservatore e che non esiste un concetto universale di simultaneità.)


@mpoeter ha suggerito che un compilatore potrebbe persino rimuovere il carico fittizio e archiviare le operazioni, anche su oggetti seq_cst.

Penso che possa essere corretto quando possono dimostrare che nulla può sincronizzarsi con un'operazione. ad esempio un compilatore che può vedere che dummy2non sfugge alla funzione può probabilmente rimuovere quel carico seq_cst.

Ciò ha almeno una conseguenza del mondo reale: se si compila per AArch64, ciò consentirebbe a un negozio seq_cst precedente di riordinare in pratica con successive operazioni rilassate, cosa che non sarebbe stato possibile con un negozio seq_cst + caricare svuotando il buffer del negozio prima di qualsiasi carichi successivi potrebbero essere eseguiti.

Naturalmente i compilatori attuali non ottimizzano affatto l'atomica, anche se ISO C ++ non lo proibisce; questo è un problema irrisolto per il comitato degli standard.

Questo è permesso, penso, perché il modello di memoria C ++ non ha un osservatore implicito o un requisito che tutti i thread concordino sull'ordinamento. Fornisce alcune garanzie basate su cache coerenti, ma non richiede la visibilità simultanea di tutti i thread.


Bel riassunto! Sono d'accordo che in pratica sarebbe probabilmente sufficiente se solo thread A ha avuto una recinzione ss-CST. Tuttavia, in base allo standard C ++ non avremmo la garanzia necessaria da cui ricaviamo l'ultimo valore set(), quindi utilizzerei comunque anche il recinto nel thread B. Suppongo che un negozio rilassato con una recinzione seq-cst genererebbe comunque quasi lo stesso codice di un negozio seq-cst.
mpoeter

@mpoeter: sì, stavo solo parlando in pratica, non formalmente. Aggiunta una nota alla fine di quella sezione. E sì, in pratica sulla maggior parte degli ISA penso che un negozio seq_cst di solito sia semplicemente un negozio (rilassato) + una barriera. O no; su POWER un negozio seq-cst fa un (pesante) sync prima del negozio, niente dopo. godbolt.org/z/mAr72P Ma carichi seq-cst necessitano di alcune barriere su entrambi i lati.
Peter Cordes,

1

nello standard ISO std :: mutex è garantito solo per acquisire e rilasciare gli ordini, non seq_cst.

Ma nulla è garantito per avere "seq_cst ordering", in quanto seq_cstnon è una proprietà di alcuna operazione.

seq_cstè una garanzia su tutte le operazioni di una data implementazione std::atomico di una classe atomica alternativa. Pertanto, la tua domanda non è corretta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.