2013-06-11 6 views
16

Se ho un singolo int che voglio scrivere da un thread e leggere da un altro, ho bisogno di usare std::atomic, per assicurare che il suo valore sia coerente tra i core, indipendentemente dal fatto che le istruzioni leggere o no scrivere ad esso sono concettualmente atomici. Se non lo faccio, potrebbe essere che il nucleo di lettura abbia un vecchio valore nella sua cache e non vedrà il nuovo valore. Questo ha senso per me.In che modo un mutex garantisce che il valore di una variabile sia coerente tra i core?

Se si dispone di un tipo di dati complesso che non può essere letto/scritto in modo atomico, è necessario proteggerne l'accesso utilizzando alcune primitive di sincronizzazione, ad esempio std::mutex. Ciò impedirà all'oggetto di entrare (o di essere letto da) uno stato incoerente. Questo ha senso per me.

Ciò che non ha senso per me è il modo in cui i mutex aiutano il problema di caching che l'atomica risolve. Sembrano esistere esclusivamente per impedire l'accesso concorrente ad alcune risorse, ma non per propagare alcun valore contenuto all'interno di quella risorsa alle cache di altri core. C'è qualche parte della loro semantica che mi è sfuggita e che tratta di questo?

+0

Non capisco quello che non si ottiene. L'atomica ha la semantica [lock mutex globale; fare op; sblocca mutex globale]. Solo alcuni hanno un supporto intrinseco così fatto in modo implicito e veloce. –

+0

Solo FYI - std :: atomic funziona con tutti i tipi di dati. In modo divertente, non si blocca se è troppo complicato, ma in quel caso ricade sulle serrature. Vedi la funzione membro 'is_lock_free()'. Tuttavia, devi stare attento se lo fai invece di serrature. –

+1

Balog, comprendo la semantica dei mutex e capisco come gli atomici fanno quello che fanno, e capisco che in C++ 11 hanno ufficialmente una semantica extra che impedisce ai core diversi di mantenere i vecchi valori nella cache. Quello che non capisco è come fanno i mutex. Impediscono l'accesso simultaneo a una cosa, ma non ho letto nulla che affermi che la cosa avrà un valore coerente nella cache di diversi core. –

risposta

4

La risposta giusta a questo è folletti magici - ad es. Funziona. L'implementazione di std :: atomic per ogni piattaforma deve fare la cosa giusta.

La cosa giusta è una combinazione di 3 parti.

In primo luogo, il compilatore ha bisogno di sa che non può spostare le istruzioni oltre i limiti [infatti può in alcuni casi, ma si assume che non lo sia].

In secondo luogo, il sottosistema della cache/memoria deve sapere - questo è generalmente fatto usando le barriere di memoria, sebbene x86/x64 in genere abbiano una memoria così forte che questo non è necessario nella stragrande maggioranza dei casi (che è un grande peccato perché è buono per il codice sbagliato in realtà andare male).

Infine, la CPU deve sapere che non è possibile riordinare le istruzioni. Le moderne CPU sono massicciamente aggressive in fase di riordino delle operazioni e assicurandosi che nella casella con thread singolo non sia notabile. Potrebbero aver bisogno di più indizi che ciò non possa accadere in determinati luoghi.

Per la maggior parte delle CPU le parti 2 e 3 si riducono alla stessa cosa: una barriera di memoria implica entrambe.La parte 1 è totalmente all'interno del compilatore, ed è compito degli scrittori del compilatore avere ragione.

Vedere Herb Sutters parlare 'Atomic Weapons' per informazioni molto più interessanti.

+0

Questa sembra la risposta più completa, grazie! Sfortunatamente ora significa che devo porre la domanda di follow-up "in che modo le barriere della memoria aumentano la coerenza" poiché non vedo come impedire il riordino può assicurare che CPU/core si assicurino che i valori che hanno memorizzato nella cache siano sincronizzati ... non fanno altro sciacquare tutto quando incontrano una barriera? Forse solo le cose hanno accesso ad alcune istruzioni intorno alla barriera? Immagino che la risposta sia di nuovo "folletti magici" :) –

+0

Inoltre, ciao Mike! Penso che siamo stati insieme a Rare per un po 'dopo che mi sono unito! –

+0

@BenHymers Le cache si sincronizzano sempre. Non ci sono istruzioni speciali per mantenere sincronizzate le cache. È fatto automaticamente e sempre. – curiousguy

6

La coerenza tra i core è assicurata da memory barriers (che impedisce anche il riordino delle istruzioni). Quando si utilizza std::atomic, non solo si accede ai dati in modo atomico, ma il compilatore (e la libreria) inseriscono anche le relative barriere di memoria.

I mutex funzionano allo stesso modo: le implementazioni mutex (ad esempio pthreads o WinAPI o cosa no) internamente inseriscono anche le barriere di memoria.

+1

Cos'è esattamente una barriera di memoria? Come funziona? O dovrebbe essere una domanda a parte? – SirGuy

+1

Questa dovrebbe essere una ricerca Google;) –

+3

@GuyGreer: Secondo me dovrebbe essere una domanda separata, ma non sono nemmeno sicuro che sia adatto per SO dal momento che l'argomento è così ampio (tipico "abbiamo bisogno di un intero libro per rispondi "). Una ricerca online è davvero un buon inizio per ottenere le basi, quindi possono essere poste * precise * domande. – syam

4

I processori multicore più moderni (inclusi x86 e x64) sono cache coherent. Se due core mantengono la stessa posizione di memoria nella cache e uno di essi aggiorna il valore, la modifica viene automaticamente propagata alle cache di altri core. È inefficiente (scrivere alla stessa riga di cache contemporaneamente da due core è molto lento) ma senza coerenza di cache sarebbe molto difficile scrivere software multithread.

E come ha detto syam, sono anche necessarie barriere di memoria. Impediscono al compilatore o al processore di riordinare gli accessi alla memoria e impongono anche la scrittura in memoria (o almeno nella cache), quando per esempio una variabile viene tenuta in un registro a causa delle ottimizzazioni del compilatore.

Problemi correlati