2017-12-21 24 views
2

Ich möchte eine Datenstruktur zwischen Threads (gcc, Linux, x86) teilen. Lassen Sie uns sagen, dass ich den folgenden Code in Thread A haben:Wie erzwingen Speicherreihenfolge mit gcc auf x86

shared_struct->a = 1; 
shared_struct->b = 1; 
shared_struct->enable = true; 

Thema B ist eine periodische Aufgabe, die für die Struktur erste enable-Flag überprüft das.

Ich denke, dass der Compiler die Schreibvorgänge in Thread A neu anordnen kann, damit Thread B inkonsistente Daten sehen kann. Ich bin vertraut mit Speicherbarrieren auf ARM, aber Wie stelle ich Schreibreihenfolge auf x86 sicher? Gibt es einen besseren Weg als volatile?

Ich möchte nur einen konsistenten Zustand in der Struktur, "flush" alles in den Speicher und setzen Sie eine Freigabe-Flag am Ende.

+5

Das ist * gar nicht * was volatil ist für. Wenn es ein atomares Flag gibt, sollten Sie C11 verwenden '' –

+0

Ich weiß - es ist ein "Nebenprodukt", dass flüchtige Zugriffe nicht neu geordnet werden. – filo

+0

@AnttiHaapala Was hat der atomare Zugriff mit der Neuordnung von Anweisungen zu tun? – Lundin

Antwort

3

Sie sollten wirklich verwenden einen Mutex (da Sie Pthread schweigen), so fügen Sie ein pthread_mutex_lock mtx; Feld innerhalb shared_struct (vergessen Sie nicht, es mit pthread_mutex_init zu initialisieren) dann

pthread_mutex_lock(&shared_struct->mtx); 
shared_struct->a = 1; 
shared_struct->b = 1; 
shared_struct->enable = true; 
pthread_mutex_unlock(&shared_struct->mtx); 

und ähnlich zu der in jedem anderen Code, der auf diese freigegebenen Daten zugreift.

Sie könnten auch in atomic operations (aber in Ihrem Fall, verwenden Sie besser eine mutex wie oben gezeigt).

Lesen Sie einige pthread tutorial.

Vermeiden Sie race conditions und undefined behavior.

wie stelle ich sicher, Schreib

Bestellung Sie tun das nicht, es sei denn, Sie sind eine Thread-Bibliothek Implementierung (und einige Teile davon sollten dann in Assembler codiert werden und verwenden futex(7)) , wie nptl(7) Implementierung von pthreads(7) in GNU glibc (oder musl-libc). Sie sollten Mutexe verwenden und nicht die Zeit verlieren, eine Thread-Bibliothek zu implementieren (also die vorhandenen zu verwenden).

Beachten Sie, dass die meisten C-Standardbibliotheken auf Linux (einschließlich glibc & MUSL-libc) sind free software, so dass Sie ihren Quellcode studieren können (wenn Sie neugierig sind, zu verstehen, wie Pthread mutexes umgesetzt werden, etc).

kann der Compiler die Schreib

Es ist nicht meist ist neu anordnen (und schon gar nicht nur) den Compiler, aber die Hardware. Lesen Sie über cache coherence. Und das Betriebssystem kann auch beteiligt sein (futex(2) manchmal von Pthread Mutex-Routinen aufgerufen).

+0

Danke - Ich habe den offensichtlichen Mutex vergessen. – filo

+1

Als eine Randnotiz, 'volatile' _does_ schützt gegen Hardware-Nachbestellung. Weder die Hardware noch der Compiler dürfen von der C-Spezifikation abweichen. Ein System, bei dem die Hardware Anweisungen neu anordnet, so dass flüchtige Variablen nicht wie vom Programmierer spezifiziert sequenziert werden, stimmt nicht überein. Wenn weder die Hardware noch der Compiler garantieren können, dass das C-Programm gemäß der "abstrakten Maschine" des C-Standards ausgeführt wird, können in C geschriebene Programme nicht auf diesem System verwendet werden. Um die Last der Speicherbarrieren auf den Programmierer zu legen, wird das System nicht konform gemacht. – Lundin

+0

Hier ist auch ein 'pthread_rwlock' angebracht, und IMO passt besser zum Anwendungsfall, sodass sich mehrere Leser-Threads nicht gegenseitig stören können. –

2

Wenn Sie nur in der Lage sein müssen enable = true einzustellen, dann gibt Ihnen stdatomic.h mit release/acquire bestellen genau das, wonach Sie fragen. (In x86-asm haben normale Stores/Loads eine Semantik zum Freigeben/Akquirieren, also ist das Blockieren der Neuordnung der Kompilierungszeit irgendwie ausreichend. Aber der richtige Weg dazu ist atomic, nicht volatile.)

Aber wenn Sie in der Lage sein möchten, enable = false zu sperren, um Leser erneut zu sperren, während Sie es ändern, dann benötigen Sie ein komplizierteres Update-Muster. Entweder erfinden Sie einen Mutex manuell mit Atomics neu (schlechte Idee; verwenden Sie stattdessen einen Standard-Bibliotheks-Mutex) oder machen Sie etwas, das einen latenzfreien Lesezugriff durch mehrere Leser zulässt, wenn kein Schreiber mitten in einem Update ist.

Entweder RCU a oder seqlock könnte hier gut sein.

Für einen Seqlock haben Sie anstelle eines enable = true/false-Flags eine Sequenznummer. Ein Leser kann einen "zerrissenen" Schreibvorgang erkennen, indem er die Sequenznummer vor und nach dem Lesen der anderen Mitglieder überprüft. (Aber dann alle Mitglieder müssen atomic, mit mindestens mo_relaxed, sonst ist es Datenrennen undefiniertes Verhalten nur aus dem Lesen sie in C, auch wenn Sie den Wert zu verwerfen.Sie benötigen auch ausreichende Reihenfolge auf die Lasten, die den Zähler überprüfen wahrscheinlich erwerben Sie auf der ersten, dann erwerben Sie auf der shared_struct->b Last, um sicherzustellen, dass die zweite Last der Sequenznummer nach ihm bestellt wird. (acquire ist nur eine Einweg-Barriere: eine Übernahme Last nach einer entspannten Last würde Ihnen nicht geben Was Sie brauchen.)

RCU macht die Leser immer komplett wartefrei, sie dereferenzieren nur einen Zeiger auf die aktuell gültige Struktur.Wiederholungen sind so einfach wie atomare Ersetzen eines Zeigers.Recycling alten Strukturen ist, wo es kompliziert wird: Sie müssen sicher sein, dass jeder Leser-Thread einen Mem-Block gelesen hat oder bevor Sie es wiederverwenden.


einfach enable = false Einstellung, bevor Sie die anderen Strukturkomponenten Ändern stoppt nicht einen Leser von enable == true zu sehen und dann inkonsistent/teilweise aktualisierten Werten für die anderen Mitglieder zu sehen, während ein Schriftsteller sie ändern. Wenn Sie dies nicht tun müssen, aber immer nur neue Objekte für den Zugriff durch andere Threads freigeben müssen, ist die von Ihnen beschriebene Sequenz in Ordnung mit atomic_store_explicit(&foo->enable, true, memory_order_release).

+0

Der 'memory_order_release'-Speicher sollte mit einem' memory_order_acquire'-Ladevorgang des Enable-Flags im Lesegerät gepaart werden, richtig? – caf

+0

@caf: ja. acquire operations (und seq_cst) operations "synchronize-with" release (und seq_cst) Operationen. http://preshing.com/20120913/acquire-and-release-semantics/ hat einige nette Details darüber, wie dies tatsächlich auf einigen echten Maschinen funktioniert, und was es tut/gibt Ihnen nicht so weit wie die Bestellung. –

Verwandte Themen