Selbst für ein einfaches 2-Faden-Kommunikation Beispiel, ich habe Schwierigkeiten, diese atomaren und memory_fence im C11-Stil zum Ausdruck bringen richtige Speicherordnung zu erhalten:C11 Speicher Zaun Verwendung
gemeinsam genutzter Daten:
volatile int flag, bucket;
Gewinde
Produzent:
while (true) {
int value = producer_work();
while (atomic_load_explicit(&flag, memory_order_acquire))
; // busy wait
bucket = value;
atomic_store_explicit(&flag, 1, memory_order_release);
}
Verbraucher dre Anzeige:
while (true) {
while (!atomic_load_explicit(&flag, memory_order_acquire))
; // busy wait
int data = bucket;
atomic_thread_fence(/* memory_order ??? */);
atomic_store_explicit(&flag, 0, memory_order_release);
consumer_work(data);
}
Soweit ich oben Code zu verstehen, Shop-in-Eimer richtig wäre bestellen -> Flag-Speicher -> Flag-Last -> Last-from-Eimer. Ich denke jedoch, dass es zwischen dem Load-from-Bucket und dem Bucket einen neuen Zustand mit neuen Daten gibt. Um eine Order zu erzwingen, die dem Bucket-Read folgt, würde ich einen expliziten atomic_thread_fence()
zwischen dem Bucket-Read und dem folgenden Atomic_store benötigen. Leider scheint es kein memory_order
Argument zu geben, um irgendetwas auf vorhergehenden Lasten zu erzwingen, nicht einmal die memory_order_seq_cst
.
Eine wirklich schmutzige Lösung könnte sein, die bucket
im Consumer-Thread mit einem Dummy-Wert neu zuzuordnen: das widerspricht dem Consumer Read-Only-Konzept.
In der älteren C99/GCC-Welt konnte ich die traditionelle __sync_synchronize()
verwenden, die meiner Meinung nach stark genug wäre.
Was wäre die bessere C11-Lösung, um diese sogenannte Anti-Abhängigkeit zu synchronisieren?
(Natürlich Ich bin mir bewusst, dass ich besser, solche Low-Level-Codierung vermeiden sollte und Nutzung stehen zur Verfügung geordnete Konstrukte, aber ich würde ... verstehen mag)
Ich bin kein C++ - Programmierer, aber (konzeptionell) bin ich nicht sicher, ob der Aufruf "atomic_thread_fence()" notwendig ist. Die Flag-Aktualisierung hat eine Freigabesemantik, die verhindert, dass irgendwelche vorhergehenden Speicheranweisungen über sie neu angeordnet werden (z. B. der Speicher zu "Daten"). Der Speicher für "Daten" hat eine Abhängigkeit von dem Lesevorgang von "Speicherbereich", so dass der Lesevorgang nicht über die Merkerfreigabe hinaus neu geordnet werden kann. Wenn der volle Zaun notwendig ist, würde ich gerne hören warum. –
Keine Antwort, also nur ein Kommentar: Es scheint so, als ob Sie C11s "atomic_flag" -Datentyp neu erfinden, der genau diese Semantik implementiert, aber letztendlich eine direktere Implementierung in Hardware hat. 'atomic_flag' ist der einzige atomare Datentyp, bei dem die Sperrung garantiert ist. Dies ist gegenüber komplexeren Operationen immer vorzuziehen. Und es würde definitiv keinen zusätzlichen Zaun benötigen, um Konsistenz zu gewährleisten. –
Mike S, deine Antwort scheint mir attraktiv, aber ... Ich dachte, dass Speicherzäune Dinge auf dem Speichersubsystem sicherstellen würden, die ld/st-Ops betreffen. Im obigen Beispiel würden 'Daten' wahrscheinlich zu einer Registervariablen werden, so dass ihre Zuweisung keine Speicheroperation erzeugt. Das würde nur die Last von Bucket für die Speichersynchronisierung belassen? (für die es keine nachfolgende C11-Speicherreihenfolge gibt?) –