Bei dem Versuch, zu verstehen, wie mit sperrfreiem Code umgegangen werden soll, habe ich versucht, eine einzige blockierungsfreie Warteschlange für Verbraucher / Einzelhersteller zu schreiben. Wie immer habe ich Papiere, Artikel und Code überprüft, vor allem wenn man bedenkt, dass dies ein heikles Thema ist.
Ich bin also auf eine Implementierung dieser Datenstruktur in der Folly-Bibliothek gestoßen, die hier zu finden ist: Ссылка
Wie bei jeder blockierungsfreien Warteschlange, die ich gesehen habe, scheint diese einen zirkularen Puffer zu verwenden, also haben wir zwei std::atomic<unsigned int>
Variablen: readIndex_
und writeIndex_
. Die readIndex_
geben den nächsten Index an, bei dem wir lesen werden, und writeIndex_
den nächsten, bei dem wir schreiben werden. Scheint einfach genug.
So scheint die Implementierung auf den ersten Blick sauber und ziemlich einfach zu sein, aber ich fand eine Sache problematisch. Einige Funktionen wie isEmpty()
, isFull()
oder guessSize()
verwenden std::memory_order_consume
, um den Wert der Indizes abzurufen.
Und um fair zu sein, ich weiß wirklich nicht, welchem Zweck sie dienen. Versteh mich nicht falsch, ich bin mir der Verwendung von std::memory_order_consume
im klassischen Fall der Abhängigkeit bewusst, die durch einen Atomzeiger führt, aber hier scheinen wir keine Abhängigkeit zu tragen! Wir haben nur Indizes, vorzeichenlose Ganzzahlen, wir erzeugen keine Abhängigkeiten. Für mich ist in diesem Szenario ein std::memory_order_relaxed
gleichwertig.
Ich traue mir jedoch nicht zu, die Speicherordnung besser zu verstehen als diejenigen, die diesen Code entwickelt haben, weshalb ich diese Frage hier stelle. Gibt es etwas, was ich vermisst oder missverstanden habe?
Ich danke Ihnen im Voraus für Ihre Antworten!
Ich dachte dasselbe vor ein paar Monaten, also habe ich diese Pull-Anfrage bereits im Oktober eingereicht und vorgeschlagen Sie ändern die std::memory_order_consume
-Lasten in std::memory_order_relaxed
, da der Verbrauch einfach keinen Sinn ergibt, da keine Abhängigkeiten vorhanden sind, die von einem Thread zu einem anderen mit diesen Funktionen übertragen werden könnten. Es ergab sich eine Diskussion, die ergab, dass ein möglicher Anwendungsfall für isEmpty()
, isFull()
und sizeGuess
Folgendes war:
Deshalb erklärten sie, dass std::memory_order_relaxed
nicht angemessen wäre und std::memory_order_consume
wäre. Dies ist jedoch nur der Fall, weil std::memory_order_consume
in allen Compilern, die ich kenne, zu std::memory_order_acquire
hochgestuft wird. Obwohl std::memory_order_consume
scheinbar die richtige Synchronisation liefert, ist es ziemlich irreführend, dies im Code zu belassen und davon auszugehen, dass es korrekt bleibt, insbesondere wenn std::memory_order_consume
jemals wie beabsichtigt implementiert werden sollte. Der obige Anwendungsfall würde bei schwächeren Architekturen nicht funktionieren, da die entsprechende Synchronisation nicht erzeugt würde.
Was sie wirklich brauchen, ist diese Ladungen std::memory_order_acquire
zu machen, damit dies wie gewünscht funktioniert, weshalb ich dies eingereicht habe andere Pull-Anforderung einige Tage zurück. Alternativ könnten sie die Übernahme Lasten aus der Schleife und verwenden Sie einen Zaun am Ende:
In beiden Fällen wird std::memory_order_consume
hier falsch verwendet.
Tags und Links c++ multithreading c++11 lock-free atomic