C'è un modo posso fare due letture atomica?
-
09-09-2019 - |
Domanda
Sto correndo in una situazione in cui ho bisogno atomica somma di due valori in memoria.Il codice che ho ereditato va come questa:
int a = *MemoryLocationOne;
memory_fence();
int b = *MemoryLocationTwo;
return (a + b) == 0;
Le letture di a e b sono atomica, e tutte le scrive altrove nel codice a queste due posizioni di memoria sono anche lockless atomica.Tuttavia il problema è che i valori delle due posizioni possono cambiare tra le due letture.
Allora, come faccio a fare questa operazione atomica?So tutto del CAS, ma tende solo richiedono la realizzazione di read-modify-write operazioni atomiche e che non è abbastanza quello che voglio fare qui.
C'è un modo per farlo, o è l'opzione migliore per il refactoring del codice, in modo che solo bisogno di controllare un valore?
Edit:Grazie, non ho detto che volevo fare questo locklessly nella prima revisione, ma alcune persone hanno notato su di esso dopo la mia seconda revisione.So che nessuno è convinto che le persone quando dicono cose come questa, ma non riesco a utilizzare il blocca praticamente.Mi piacerebbe emulare un mutex con atomics e che sarebbe più lavoro di refactoring del codice per tenere traccia di un valore invece di due.
Per ora il mio metodo di indagine coinvolge approfittando del fatto che i valori sono consecutivi e li afferra atomicamente con una versione a 64 bit di lettura, che sono certo sono atomica sul mio piattaforme di destinazione.Se qualcuno ha idee nuove, si prega di contribuire!Grazie.
Soluzione
Se avete davvero bisogno di assicurarsi che a
e b
non cambia mentre si sta facendo questo test, quindi è necessario utilizzare la stessa per la sincronizzazione tutti accesso a a
e b
.Questa è la tua unica scelta.Ogni operazione di lettura e scrittura su di uno di questi valori è necessario utilizzare la stessa recinzione, sincronizzatore, semaforo, timeslice di blocco, o qualunque sia il meccanismo utilizzato.
Con questo, è possibile garantire che se si:
memory_fence_start();
int a = *MemoryLocationOne;
int b = *MemoryLocationTwo;
int test = (a + b) == 0;
memory_fence_stop();
return test;
quindi a
non cambia durante la lettura b
.Ma, ancora una volta, è necessario utilizzare lo stesso meccanismo di sincronizzazione per tutti accesso a a
e per b
.
Per riflettere un secondo di modifica alla tua domanda che siete alla ricerca di un lock-free metodo, beh, dipende dal processore utilizzato e per quanto tempo a
e b
sono, e se queste locazioni di memoria consecutive e allineati correttamente.
Supponendo che questi sono consecutive in memoria e 32 bit ciascuno e che il tuo processore ha un atomic 64-bit, quindi è possibile l'emissione atomica a 64 bit di lettura per leggere i due valori, analizzare i due valori al di fuori del valore a 64 bit, fare la matematica e restituire ciò che si desidera tornare.Supponendo di non aver mai bisogno di un aggiornamento atomico a "a
e b
allo stesso tempo," ma solo atomica aggiornamenti "a
"o a "b
"in isolamento, quindi questo farà quello che vuole, senza blocchi.
Altri suggerimenti
Si dovrebbe fare in modo che tutto il mondo uno dei due valori sono stati letti o scritti, sono stati circondati da una barriera di memoria (blocco o sezione critica).
// all reads...
lock(lockProtectingAllAccessToMemoryOneAndTwo)
{
a = *MemoryLocationOne;
b = *MemoryLocationTwo;
}
...
// all writes...
lock(lockProtectingAllAccessToMemoryOneAndTwo)
{
*MemoryLocationOne = someValue;
*MemoryLocationTwo = someOtherValue;
}
Se si prendono di mira 86, è possibile utilizzare il supporto a 64 bit confrontare / scambio e confezionare entrambi int di in una sola parola a 64 bit.
In Windows, si dovrebbe fare questo:
// Skipping ensuring padding.
union Data
{
struct members
{
int a;
int b;
};
LONGLONG _64bitData;
};
Data* data;
Data captured;
do
{
captured = *data;
int result = captured.members.a + captured.members.b;
} while (InterlockedCompareExchange64((LONGLONG*)&data->_64bitData,
captured._64BitData,
captured._64bitData) != captured._64BitData);
davvero brutto. Io suggerirei di usare un blocco -. Molto più gestibile
EDIT: Per aggiornare e leggere le singole parti:
data->members.a = 0;
fence();
data->members.b = 0;
fence();
int captured = data->members.a;
int captured = data->members.b;
Non c'è davvero nessun modo per fare questo senza una serratura. Non ci sono i processori hanno una doppia lettura atomica, per quanto ne so.