Può acquisire carichi riordinare con altre operazioni di acquisizione? cppreference dice che solo non atomici e rilassati sono ordinati per acquisizione

Aug 22 2020

Secondo C ++ Reference , mutex.lock()è memory_order_acquireun'operazione ed mutex.unlock()è memory_order_releaseun'operazione.

Tuttavia, memory_order_acquiree memory_order_releasesono 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_releaseun carico atomico nel thread B dalla stessa variabile memory_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 Ariordinato prima di mu.lock()e il thread può essere bletto xcome 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

4 PeterCordes Aug 23 2020 at 06:25

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_cstpotrebbe 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' aavvio del thread è lento, il thread bpotrebbe entrare prima nella sua sezione critica e stampare + leggere xprima 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_readmemorizzato 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.