2016-04-24 16 views
14

mia comprensione di std::memory_order_acquire e std::memory_order_release è la seguente:recinzioni di memoria: acquisire/carico e rilasciare/negozio

Acquisisci significa che nessun accessi alla memoria che appaiono dopo la recinzione acquisiscono possono essere riordinati a prima della recinzione .

uscita significa che nessun accessi alla memoria che appaiono prima la recinzione di rilascio possono essere riordinati per dopo la recinzione.

Quello che non capisco è il motivo per cui con la libreria atomica C++ 11 in particolare, la fence di acquisizione è associata alle operazioni di caricamento, mentre la fence fence è associata alle operazioni di archiviazione.

Per chiarire, la libreria C++ 11 <atomic> consente di specificare le recinzioni di memoria in due modi: o è possibile specificare un recinto come un argomento in più per un'operazione atomica, come:

x.load(std::memory_order_acquire); 

Oppure si può usare std::memory_order_relaxed e specificare il recinto a parte, come:

x.load(std::memory_order_relaxed); 
std::atomic_thread_fence(std::memory_order_acquire); 

Quello che non capisco è, date le definizioni di cui sopra di acquisire e rilasciare, perché C++ 11 associare specificamente acquisiscono con carico e versione con negozio? Sì, ho visto molti degli esempi che mostrano come è possibile utilizzare un acquisisci/carica con un release/store per sincronizzare i thread, ma in generale sembra che l'idea di acquisire le fence (evitare il riordino della memoria dopo l'istruzione) e il rilascio recinti (previene il riordino della memoria prima dell'affermazione) è ortogonale all'idea di carichi e depositi.

Quindi, perché, per esempio, non il compilatore lasciatemi dire:

x.store(10, std::memory_order_acquire); 

mi rendo conto che posso realizzare quanto sopra utilizzando memory_order_relaxed, e poi una atomic_thread_fence(memory_order_acquire) dichiarazione separata, ma ancora una volta, perché ci riesco Posso usare lo store direttamente con memory_order_acquire?

Un caso d'uso possibile per questo potrebbe essere se voglio assicurare che qualche negozio, dicono x = 10, accade prima qualche altra dichiarazione esegue che potrebbe influenzare altri thread.

+1

In un tipico algoritmo lock-free, si legge un atomico per verificare se una risorsa condivisa è pronta per il consumo (pronta per essere acquisita) e si scrive un atomico per indicare che una risorsa condivisa è pronta per essere utilizzata (per rilasciare la risorsa). Non vuoi che le letture della risorsa condivisa si spostino prima che la guardia atomica sia controllata; e non vuoi che l'inizializzazione della risorsa da condividere sia spostata dopo che l'atomico è stato scritto, indicando il rilascio. –

+0

Nell'esempio solo 'atomic_thread_fence (std :: memory_order_acquire)' è una vera barriera. Vedi ** 1.10: 5 Esecuzioni multi-thread e dati razze [intro.multithread] ** nello standard, che dice (citando la bozza n3797) _ "Un'operazione di sincronizzazione senza una posizione di memoria associata è una fence e può essere o una acquisire recinzione, una recinzione di rilascio, o entrambe una recinzione di acquisizione e di rilascio. "_ Al contrario,' x.load (std :: memory_order_acquire) 'è una operazione _atomica_ che esegue un'operazione _acquire_ su' x', sarebbe una _sincronizzazione operazione_ se il valore corrisponde a un negozio _release_ in x. – amdn

+0

Nell'introduzione lo standard (bozza n3797) non limita le operazioni di acquisizione ai carichi e rilascia le operazioni ai negozi. Questo è sfortunato. Devi andare alla clausola ** 29.3: 1 Ordine e consistenza [atomics.order] ** per trovare _ "memory_order_acquire, memory_order_acq_rel e memory_order_seq_cst: un'operazione di caricamento esegue un'operazione di acquisizione sulla posizione di memoria interessata" _ e _ "memory_order_release , memory_order_acq_rel e memory_order_seq_cst: un'operazione di archiviazione esegue un'operazione di rilascio nella posizione di memoria interessata "_ – amdn

risposta

10

Dire che scrivo alcuni dati, e poi scrivo un'indicazione che i dati sono pronti. È imperativo che nessun altro thread che vede l'indicazione che i dati siano pronti non vede la scrittura dei dati stessi. Quindi le scritture precedenti non possono superare quella scrittura.

Dire che alcuni dati sono pronti. È imperativo che tutte le letture che emetto dopo aver visto ciò avvengano dopo la lettura che ha visto che i dati erano pronti. Quindi le letture successive non possono muoversi dietro quella lettura.

Quindi, quando si esegue una scrittura sincronizzata, in genere è necessario assicurarsi che tutte le scritture effettuate prima siano visibili a chiunque veda la scrittura sincronizzata. E quando si esegue una lettura sincronizzata, è in genere indispensabile che tutte le letture che fai dopo avvengano dopo la lettura sincronizzata.

Oppure, per dirla in un altro modo, un acquisito sta leggendo in genere che è possibile prendere o accedere alla risorsa, e le successive letture e scritture non devono essere spostate prima di esso. Una versione in genere sta scrivendo che hai finito con la risorsa, e le scritture precedenti non devono essere spostate dopo di essa.

-1

std::memory_order_acquire recinzione assicura solo tutti carico funzionamento dopo la barriera non è riordinato prima di qualsiasi carico funzione prima della recinzione, così memory_order_acquirenon può garantire il negozio è visibile per altri thread quando vengono eseguiti dopo carichi. Questo è il motivo per cui memory_order_acquire non è supportato per l'operazione di archiviazione, potrebbe essere necessario memory_order_seq_cst per ottenere l'acquisizione del negozio.

In alternativa, si può dire

x.store(10, std::memory_order_releaxed); 
x.load(std::memory_order_acquire); // this introduce a data dependency 

per assicurare tutti i carichi non riordinati prima che il negozio. Di nuovo, la recinzione non funziona qui.

Inoltre, l'ordine di memoria nell'operazione atomica potrebbe essere più economico di una barriera di memoria, poiché garantisce solo l'ordine relativo all'istruzione atomica, non tutte le istruzioni prima e dopo la recinzione.

Vedere anche formal description e explanation per dettagli.

+0

La prima frase non è corretta (-1). In realtà, * qualsiasi accesso alla memoria * che segue una fence di acquisizione non può essere riordinato con nessuna operazione di caricamento che precede quella fence. (Viceversa, qualsiasi accesso alla memoria che precede un recinto di rilascio non può essere riordinato con nessuna operazione di archivio che segue quella recinzione.) –

+0

@JohnWickerson In realtà 'memory_order_releaxed' assicura solo i carichi dopo che la fence si verifica dopo qualsiasi operazione atomica o fence con' memory_order_release'. Non fornisce alcun ordine nei negozi dopo la recinzione. Vedi la sezione sulla sincronizzazione di atomic-fence in ['atomic_thread_fence'] (http://en.cppreference.com/w/cpp/atomic/atomic_thread_fence) – user1887915

+0

Interessante! Credo che il sito web cppreference.com a cui ti riferisci sia in realtà sbagliato qui. Secondo lo standard ufficiale C11, rilasciare e acquisire recinti si comportano nel modo in cui ho descritto. –