2016-01-23 16 views
5

Ich habe einen Ringspeicher, die ein Verbraucher und ein Hersteller-Services und nutzt zwei ganzen Zahlen, neue Daten zu erfassen:Least-restriktive Speicherordnung für Single-Producer, Single-Consumer-Ringpuffer?

_lastReadIndex 
_lastWrittenIndex 

so gibt es ungelesene Daten in den Ringpuffer, wenn diese beiden Werte nicht gleich sind.

Der Producer Inkrementen (Moduli und mit dem Ringpuffer Größe Wrap-around) _lastWrittenIndex, wenn ein Element mit dem Ringpuffer hinzugefügt wird.

Der Verbraucher dreht, Lese beiden Werte, für neue Daten überprüft und wenn es, es wird Zuwachs (und Modul) _lastReadIndex.

Die drei hervorgehobenen Begriffe unterstreichen die Anforderungen in Bezug auf Multithreading und Speicherbarrieren.

Wie weit kann ich die Speicherbestellung für dieses Design unter Berücksichtigung von Intels Speichermodell entspannen? Ich glaube, dass das Speichermodell von Intel ermöglicht, dass Ladungen mit früheren Speichern an verschiedene Adressen neu geordnet werden können.

EDIT die C++ 11 Atom Bibliothek std::memory_order_xxxx etc

+0

Also wollen Sie C++ 11 Atomics oder architekturabhängige Entscheidung (mit Assembler) für Intel? Im ersten Fall ist die Architektur nicht verwandt, in der zweiten - "C++" - Tag ist nicht verwandt. – Tsyvarev

+0

Sorry, C++ 11 Bibliotheken – user997112

+0

Die Sache ist, dass Ihre Indizierung im Ring-Puffer, hängt davon ab, den _lastReadIndex-Wert und ** dann ** das Modulo zu lesen. Es sind also zwei getrennte Aktionen. Wenn nur der Wert in _lastReadIndex gelesen werden würde, würden "acquire" zum Lesen und "release" für die Schreibvorgänge ausreichen. – ViNi89

Antwort

1

Ein paar Dinge, die Sie vor, etwas zu tun haben sonst:

Modulus die Lese- und Schreibpunkte, aber halten _lastReadIndex und _lastWrittenIndex intakt zu wissen, wie viele Daten, die Sie zur Verfügung haben, wie viel verloren ist, oder vielleicht blockieren Sie auf Writer, wenn es den Leser nach dem vollen Zyklus überläuft.

Und, sehr wichtig, vermeiden Sie es, so viel wie möglich zu teilen - legen Sie die Reader und Writer-Variablen auf separaten Cache-Zeilen.

Nun zu Ihrer Frage:

Wenn Sie versuchen, tragbar zu sein, der Speicher, den Sie bestellen würde in Ihrem Code benötigen, sollten nicht die Architektur betrachten. Die atomaren Standardfunktionen können dafür sorgen. Sie müssen nur sicherstellen, dass Daten im Puffer verfügbar sind, bevor Sie den Schreibindex inkrementieren, was bedeutet, dass Semantik für das Inkrement freigegeben wird. Sie müssen auch sicherstellen, dass der Writer Daten in den Speicher schreibt und nicht optimiert, um nur in Registern zu bleiben.

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

Auf x86/64, wird dies das gleiche wie:

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

Wenn das Schreiben von Code, der _lastWrittenIndex nicht mehr als unbedingt notwendig zugreift, wie oben, können Sie auch erklären, es flüchtig aber halten im Verstand wird die Barriere noch benötigt!

+0

Es wäre erwähnenswert, dass, wenn der Verbraucher _lastWrittenIndex liest, muss ein atomic_load mit memory_order_acquire verwendet werden. Auch _lastReadIndex benötigt eine ähnliche Behandlung, da der Verbraucher tatsächlich leere Slots an den Hersteller überträgt. –