2016-01-23 14 views
5

Ho un ringbuffer che assiste un consumatore e un produttore e utilizza due numeri interi per rilevare nuovi dati:Ordinamento di memoria meno restrittivo per il ringbuffer singolo produttore, singolo consumatore?

_lastReadIndex 
_lastWrittenIndex 

quindi non c'è dati non letti nel ringbuffer quando questi due valori non sono uguali.

Il Producer incrementi (e moduluses con la dimensione ringbuffer a avvolgenti) _lastWrittenIndex quando un elemento viene aggiunto alla ringbuffer.

gira Il Consumatore, lettura entrambi i valori, la verifica per i nuovi dati e quando c'è, lo farà incremento (e modulo) _lastReadIndex.

I tre termini evidenziati sottolineano i requisiti relativi al multithreading e alle barriere della memoria.

In che misura posso rilassare l'ordine di memoria per questo modello, tenendo conto del modello di memoria Intel? Credo che il modello di memoria di Intel consenta di riordinare i carichi con i negozi precedenti a indirizzi diversi?

EDIT utilizzando la libreria atomica C++ 11 std::memory_order_xxxx ecc

+0

Quindi vuoi atomici C++ 11, o decisioni dipendenti dall'architettura (usando l'assemblatore) per Intel? Nel primo caso l'architettura non è correlata, nel secondo - il tag 'C++' non è correlato. – Tsyvarev

+0

Siamo spiacenti, librerie C++ 11 – user997112

+0

Il fatto è che l'indicizzazione nel buffer circolare dipende dalla lettura del valore '_lastReadIndex' e ** quindi ** del modulo. Quindi sono due azioni separate. Se fosse solo per leggere il valore in '_lastReadIndex',' acquire' per read e 'release' per le scritture sarebbero sufficienti. – ViNi89

risposta

1

Un paio di cose che dovete fare prima di ogni altra cosa:

Modulus i punti di lettura e scrittura, ma mantenere _lastReadIndex e _lastWrittenIndex intatta sapere come molti dati che hai a disposizione, quanto è stato perso, o forse potrebbero bloccare lo scrittore se supera il lettore dopo il ciclo completo.

E, cosa molto importante, evitare di condividere il più possibile - inserire le variabili del lettore e dello scrittore su linee di cache separate.

Ora, alla tua domanda:

Se si sta cercando di essere portatile, la memoria che l'ordinazione sarebbe richiedono nel codice non dovrebbe prendere in considerazione l'architettura. Le funzioni atomiche standard possono occuparsene. È necessario solo assicurarsi che i dati siano disponibili nel buffer prima di incrementare l'indice di scrittura, il che significa semantica di rilascio sull'incremento. Devi anche assicurarti che lo scrittore scriva i dati in memoria e non sia ottimizzato per rimanere solo nei registri.

newIndex = _lastWrittenIndex+1; 
buffer[newIndex % bufSize] = newData; 
atomic_store(&_lastWrittenIndex, newIndex, memory_order_release); 

On x86/64, questo sarà lo stesso:

newIndex = _lastWrittenIndex+1; 
buffer[newIndex % bufSize] = newData; 
// release semantics means reorder barrier before action: 
barrier(); // translates to `asm volatile("":::"memory");` 
*(volatile int*)_lastWrittenIndex = newIndex; 

Quando si scrive codice che accede _lastWrittenIndex non più del necessario, come sopra, si può anche dichiarare volatili, ma mantenere in mente è ancora necessaria la barriera!

+0

Vale la pena sottolineare che quando il consumatore legge _lastWrittenIndex, deve utilizzare un atomic_load con memory_order_acquire. Inoltre, _lastReadIndex richiede un trattamento simile, perché in effetti il ​​consumatore sta trasmettendo spazi vuoti al produttore. –

Problemi correlati