2009-04-10 7 views
6

Sto correndo in una situazione in cui ho bisogno della somma atomica di due valori in memoria. Il codice che ho ereditato va come questa:C'è un modo per rendere due letture atomiche?

int a = *MemoryLocationOne; 
memory_fence(); 
int b = *MemoryLocationTwo; 
return (a + b) == 0; 

L'individuo legge di A e B sono atomiche, e tutte le scritture in altre parti del codice per queste due posizioni di memoria sono lockless anche atomica. Tuttavia il problema è che i valori delle due posizioni possono cambiare e cambiano tra le due letture.

Quindi, come faccio a rendere questa operazione atomica? So tutto di CAS, ma tende a coinvolgere solo le operazioni di lettura-modifica-scrittura atomiche e non è proprio quello che voglio fare qui.

C'è un modo per farlo, o è l'opzione migliore per refactoring del codice in modo che ho solo bisogno di controllare un valore?

Modifica: Grazie, non ho detto che volevo farlo senza chiave nella prima revisione, ma alcune persone lo hanno rilevato dopo la mia seconda revisione. So che nessuno crede alle persone quando dicono cose come queste, ma non posso usare le serrature praticamente. Dovrei emulare un mutex con atomics e sarebbe più lavoro di refactoring del codice per tenere traccia di un valore anziché di due.

Per ora il mio metodo di investigazione consiste nel trarre vantaggio dal fatto che i valori sono consecutivi e li afferrano atomicamente con una lettura a 64 bit, che sono sicuro siano atomici sulle mie piattaforme di destinazione. Se qualcuno ha nuove idee, per favore contribuisci! Grazie.

risposta

3

Se veramente necessario assicurarsi che a e b non cambiano mentre si sta facendo questo test, allora avete bisogno di utilizzare la stessa sincronizzazione per tutto accesso a a e b. Questa è la tua unica scelta. Ogni lettura e ogni scrittura su uno di questi valori deve utilizzare la stessa barriera di memoria, sincronizzatore, semaforo, blocco timeslice o qualsiasi altro 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; 

poi a non cambierà mentre si sta leggendo b. Ma ancora una volta, è necessario utilizzare lo stesso meccanismo di sincronizzazione per all accesso a a e a b.

Per riflettere una modifica successiva alla tua domanda che stai cercando un metodo lock-free, beh, dipende interamente dal processore che stai utilizzando e per quanto tempo sono a e e se queste posizioni di memoria o meno sono consecutivi e allineati correttamente.

Supponendo che siano consecutivi in ​​memoria e 32 bit ciascuno e che il processore abbia una lettura atomica a 64 bit, quindi è possibile emettere una lettura atomica a 64 bit per leggere i due valori in, analizzare i due valori fuori dalla Valore a 64 bit, eseguire i calcoli e restituire ciò che si desidera restituire.Supponendo che non sia necessario un aggiornamento atomico a "a e allo stesso tempo" ma solo aggiornamenti atomici a "a" oa "b" in isolamento, quindi ciò farà ciò che si desidera senza blocchi.

+0

Capita di essere consecutivi 32 indirizzi bit e processori devo codificare per lavorare su avere atomico a 64 bit leggono se correttamente allineati, così questa sembra la via da seguire. –

+0

Ah, in questo caso puoi scrivere un assembly o semplicemente controllare l'output del tuo compilatore per verificare che l'uso di un tipo a 64 bit farà la cosa giusta. In tal caso, puoi andare in blocco. – Eddie

3

È necessario assicurarsi che ovunque uno dei due valori sia stato letto o scritto, che siano 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; 
} 
1

Non c'è davvero modo di farlo senza un lucchetto. Nessun processore ha una doppia lettura atomica, per quanto ne so.

+0

Anche se un processore avesse una doppia lettura atomica, sarebbe utile solo se i due indirizzi di memoria in questione fossero correttamente allineati e consecutivi. – Eddie

+0

No, un processore potrebbe consentire una lettura atomica di due posizioni di memoria arbitrarie. – Zifre

+0

Non sono a conoscenza di alcun processore che permetta una lettura atomica garantita di posizioni di memoria arbitrarie non consecutive, anche in un ambiente multiprocessore, ma mi prenderò la parola per questo. – Eddie

3

Se si sta indirizzando x86, è possibile utilizzare il supporto di confronto/scambio a 64 bit e comprimere entrambi i file int in una singola parola a 64 bit.

Su 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. Suggerirei di usare un lucchetto - molto più mantenibile.

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; 
+0

Questo è vero, ma sei fregato quando devi aggiornare solo uno di questi elementi. –

+1

No, si esegue la lettura/scrittura atomica di base sulle parti a 32 bit. – Michael

+1

E nel caso in cui non fosse chiaro, la mia risposta era di mostrare come si poteva fare - consiglio sempre di evitare algoritmi senza lock. – Michael

Problemi correlati