Ho ascoltato e letto diversi articoli, discorsi e domande su StackOverflow std::atomic
e vorrei essere sicuro di averlo capito bene. Perché sono ancora un po 'confuso con la riga della cache che scrive la visibilità a causa di possibili ritardi nei protocolli di coerenza della cache MESI (o derivati), buffer di archivio, code non valide e così via.
Ho letto che x86 ha un modello di memoria più forte e che se una invalidazione della cache viene ritardata, x86 può ripristinare le operazioni avviate. Ma ora sono interessato solo a ciò che dovrei assumere come programmatore C ++, indipendentemente dalla piattaforma.
[T1: thread1 T2: thread2 V1: variabile atomica condivisa]
Capisco che std :: atomic garantisce che,
(1) Non si verificano corse di dati su una variabile (grazie all'accesso esclusivo alla riga della cache).
(2) A seconda di quale memory_order utilizziamo, garantisce (con barriere) che si verifichi la coerenza sequenziale (prima di una barriera, dopo una barriera o entrambe).
(3) Dopo una scrittura atomica (V1) su T1, una RMW atomica (V1) su T2 sarà coerente (la sua riga della cache sarà stata aggiornata con il valore scritto su T1).
Ma come menziona il primer di coerenza della cache ,
L'implicazione di tutte queste cose è che, per impostazione predefinita, i carichi possono recuperare dati non aggiornati (se una corrispondente richiesta di invalidazione si trovava nella coda di invalidazione)
Quindi, è il seguente corretto?
(4) std::atomic
NON garantisce che T2 non legga un valore "stantio" su una lettura atomica (V) dopo una scrittura atomica (V) su T1.
Domande se (4) è giusto: se la scrittura atomica su T1 invalida la riga della cache indipendentemente dal ritardo, perché T2 sta aspettando che l'invalidazione sia efficace quando viene eseguita un'operazione RMW atomica ma non su una lettura atomica?
Domande se (4) è sbagliato: quando un thread può leggere un valore "stantio" e "è visibile" nell'esecuzione, allora?
Apprezzo molto le tue risposte
Aggiornamento 1
Quindi sembra che mi sia sbagliato su (3) allora. Immagina il seguente interleave, per un iniziale V1 = 0:
T1: W(1)
T2: R(0) M(++) W(1)
Anche se in questo caso l'RMW di T2 si verifica interamente dopo W (1), può comunque leggere un valore "stantio" (ho sbagliato). In base a ciò, Atomic non garantisce la piena coerenza della cache, ma solo la coerenza sequenziale.
Aggiornamento 2
(5) Ora immagina questo esempio (x = y = 0 e sono atomici):
T1: x = 1;
T2: y = 1;
T3: if (x==1 && y==0) print("msg");
in base a ciò di cui abbiamo parlato, vedere "msg" visualizzato sullo schermo non ci fornirebbe informazioni oltre al fatto che T2 è stato eseguito dopo T1. Quindi potrebbe essere avvenuta una delle seguenti esecuzioni:
- T1 <T3 <T2
- T1 <T2 <T3 (dove T3 vede x = 1 ma non y = 1 ancora)
è giusto?
(6) Se un thread è sempre in grado di leggere valori "non aggiornati", cosa accadrebbe se prendessimo il tipico scenario di "pubblicazione" ma invece di segnalare che alcuni dati sono pronti, facciamo esattamente il contrario (eliminiamo i dati)?
T1: delete gameObjectPtr; is_enabled.store(false, std::memory_order_release);
T2: while (is_enabled.load(std::memory_order_acquire)) gameObjectPtr->doSomething();
dove T2 continuerebbe a usare un ptr eliminato fino a quando is_enabled è falso.
(7) Inoltre, il fatto che i thread possano leggere valori "non aggiornati" significa che un mutex non può essere implementato con un solo atomico senza lock, giusto? Richiederebbe un meccanismo di sincronizzazione tra i thread. Richiederebbe un atomico bloccabile?