Warum ist die Ruhekonsistenz zusammengesetzt, die sequentielle Konsistenz jedoch nicht?


7

Ich habe Probleme beim Vergleich dieser beiden Speicherkonsistenzmodelle.

Im Wesentlichen aus Gründen der sequentiellen Konsistenz denke ich an echten Code wie diesen:

int x, y;

void ThreadA()
{
   x = 20; //Write
   int a = y; //Read
}

void ThreadB()
{
    y = 20;
    int b = x;
}

In einer sequentiellen Konsistenz Umgebung ist es unmöglich aoder bnicht 20 zu sein , die entweder ist a = 20, b = 20oder a = 20 && b = 20.

Aber wie passt die Ruhekonsistenz zu diesem Beispiel und warum ist sie kompositorisch?

Antworten:


10

Entschuldigung für die späte Antwort, aber ich habe gerade die Frage gefunden (Fragen in der Tat). Ich studiere auch Parallelität und werde versuchen, einige Ideen mit Ihnen zu teilen.

Beginnen wir zunächst mit der sequentiellen Konsistenz. Ein Modell hat diese Eigenschaft, wenn Operationen in der Programmreihenfolge wirksam zu werden scheinen. Mit anderen Worten, die Reihenfolge, in der Codezeilen ausgeführt werden, ist die in der Quelldatei angegebene. Diese Voraussetzung ist threadspezifisch: Operationen mit unterschiedlichen Threads hängen nicht mit der Programmreihenfolge zusammen. Die Eigenschaft gewährt also Folgendes: Operationen, die von demselben Thread ausgegeben werden, werden in derselben Reihenfolge ausgeführt, die im Thread-Quellcode angegeben ist. Operationen, die von verschiedenen Threads ausgegeben werden, können in beliebiger Reihenfolge ausgeführt werden. Diese Voraussetzung mag offensichtlich erscheinen, ist jedoch nicht immer der Fall (Compiler-Optimierungen können die Reihenfolge ändern, in der Operationen ausgegeben werden, wodurch sich die Programmreihenfolge von der Quelle unterscheidet).

Ihr Beispiel ist richtig, aber lassen Sie mich Ihnen einige andere geben. Betrachten Sie dieses Programm P1 (ich werde jede Zeile als einfache Referenz markieren):

   int x = 1;

   void ThreadA()
   {
A1:    x = x * 2;
A3:    int a = x;
   }

   void ThreadB()
   {
B2:    x = 20;
   }

Gibt es eine sequentielle Ausführung, bei der am Ende a = 40 ist? Ja: B2, A1, A3.

B2 und A1 können in beliebiger Reihenfolge ausgeführt werden (sie gehören zu verschiedenen Threads). A1 wird vor A3 ausgeführt (Programmreihenfolge = Quellcodereihenfolge).

Betrachten Sie nun dieses Programm P2:

   int y = 1;

   void ThreadA()
   {
A2:    y = 40;
   }

   void ThreadB()
   {
B1:    y = y / 2;
B3:    int b = y;
   }

Gibt es eine sequentielle Ausführung, bei der am Ende b = 20 ist? Ja: A2, B1, B3.

Was ist mit Komposition? Lassen Sie uns P1 und P2 zusammenstellen. Wir bekommen P1∘P2:

   int x = 1;
   int y = 1;

   void ThreadA()
   {
A1:    x = x * 2;
A2:    y = 40;
A3:    int a = x;
   }

   void ThreadB()
   {
B1:    y = y / 2;
B2:    x = 20;
B3:    int b = y;
   }

Wenn sequentielle Konsistenz kompositorisch wäre, sollte es eine Ausführung geben, bei der am Ende a = 40 und b = 20 ist. Ist dies der Fall? Lassen Sie uns einen formellen Beweis geben, warum das nicht der Fall sein kann. Ich schreibe "o1 → o2", um zu sagen, dass die Operation o1 vor der Operation o2 ausgeführt werden muss.

In P1∘P2 muss aufgrund der sequentiellen Konsistenz Folgendes gelten: A1 -> A2 und B1 -> B2 (Programmreihenfolge pro Thread). Um a = 40 zu erhalten, muss auch B2 -> A1 gelten. Um b = 20 zu erhalten, muss auch A2 -> B1 gelten. Können Sie die Vorrangkette sehen? A1 -> A2 -> B1 -> B2 -> A1 -> ...

P1 und P2 waren nacheinander konsistent, ihre Zusammensetzung P1∘P2 jedoch nicht. Sequentielle Konsistenz ist nicht kompositorisch. Das von Ihnen angegebene Beispiel war nicht schwierig genug, um diese Tatsache zu zeigen.

Betrachten wir nun die Ruhekonsistenz. Es fällt mir schwer, diese Eigenschaft zu erklären, ohne das objektorientierte Paradigma zu verwenden. Tatsächlich kann die Ruhekonsistenz in Bezug auf ausstehende Methodenaufrufe für Objekte leicht verstanden werden. Trotzdem werde ich versuchen, mich an das imperative Paradigma zu halten (ausstehende Methodenaufrufe werden zu Funktionen, die gestartet, aber nicht abgeschlossen werden, Objekte werden zu Variablen, die an Funktionen beteiligt sind).

Ein Funktionsaufruf besteht aus einem Aufruf und einer Antwort. Eine Funktion steht noch aus, wenn sie von einem Thread aufgerufen wurde, aber noch keine Antwort auf einen solchen Thread zurückgegeben hat. Eine Ruhephase für eine Variable ist ein Zeitintervall, in dem keine ausstehenden Funktionsaufrufe (von einem Thread) ausgeführt werden. Ein Modell verfügt über die Eigenschaft "Ruhekonsistenz", wenn Funktionsaufrufe, die mit derselben Variablen ausgeführt werden und durch einen Ruhezeitraum getrennt sind, in ihrer Echtzeitreihenfolge (die durch den Quellcode angegebene) wirksam zu werden scheint. Unter dem umgekehrten Gesichtspunkt besagt die Definition, dass Operationen an derselben Variablen, die von Funktionen ausgeführt werden, die gleichzeitig von verschiedenen Threads aufgerufen werden (nicht durch Ruhe getrennt), in beliebiger Reihenfolge ausgeführt werden können.

Ich habe viele Stunden lang versucht, ein aussagekräftiges Beispiel zu entwerfen, indem ich nur Lese- / Schreiboperationen verwendete, um Ihnen den Unterschied zwischen ruhender und sequentieller Konsistenz zu zeigen, aber es gelang mir nicht. Lassen Sie mich ein anderes Beispiel mit Mengen verwenden. Ich werde einen Bitvektor verwenden, um zu verfolgen, welche ganzen Zahlen (von 0 bis 4) in der Menge sind:

   int set[5] = {0, 0, 0, 0, 0};  // 0 if i-th item is absent, 1 otherwise

   void add(int number) {
L1:    set[number] = 1;
L2:    temp foo = set[0];
   }

   void remove(int number) {
       set[number] = 0;
   }

   int contains(int number) {
       return set[number] == 1;
   }

   void ThreadA()
   {
A1:    add(1);
   }

   void ThreadB()
   {
B1:    add(2);
B2:    remove(2);
B3:    int res = contains(2);
   }

Gibt es eine sequentielle Ausführung, bei der am Ende res = 1 ist? Nein: Aufgrund der sequentiellen Konsistenz B1 -> B2 und B2 -> B3, also B1 -> B3. Also wird remove (2) immer nach add (2) ausgeführt und enthält (2) gibt immer 0 zurück.

Gibt es eine ruhende Ausführung, bei der am Ende res = 1 ist? Ja! Betrachten Sie diese Ausführung:

Thread A ruft add (1) bei A1 auf.

Thread A führt L1 aus (aber nicht L2). // Da ein Satz auf dem Set ansteht, kann Thread B jetzt B2 vor B1 ausführen, da diese Aufrufe auch den Satz betreffen

Thread B ruft B2 auf. // Aufruf + Antwort

Thread B ruft B1 auf. // Aufruf + Antwort

Thread A führt L2 aus und add (1) antwortet. // Jetzt ist Ruhe am Set

Thread B führt B3 aus. // Aufruf + Antwort

Jetzt ist res = 1.

Leider kann ich die neueste Frage, warum Ruhekonsistenz kompositorisch ist, immer noch nicht beantworten: Ich habe Ihre Frage gefunden, als ich tatsächlich nach dieser genauen Antwort gesucht habe. Wenn mir etwas einfällt, werde ich es Sie wissen lassen.

Eine gute Lektüre zu diesem Thema finden Sie in Kapitel 3.3 des Buches "Die Kunst der Multiprozessor-Programmierung" von Herlihy und Shavit.

EDIT: Ich habe gerade eine großartige Seite gefunden, auf der erklärt wird, warum Ruhekonsistenz kompositorisch ist. Und das ist eine weitere sehr gute Lektüre!

EDIT2: Kleiner Fehler im Beispiel für die Kompositionsfähigkeit der sequentiellen Konsistenz behoben.


Tolle Antwort auf eine schwierige Frage Prost! (Ich dachte, es wäre eine Berührung, bei der viele Monate lang niemand geantwortet hat: P).
William

Ich kann mich individuell mit der Ruhekonsistenz und der sequentiellen Konsistenz befassen (ebenso wie mit der strengen Konsistenz), aber ich finde es äußerst schwierig, sie miteinander in Beziehung zu setzen. Obwohl in Ihrem ersten Beispiel, wenn die Ausführung B2, A1, A3 war, in Echtzeit alles andere als a = 40 unter strenger Konsistenz unmöglich wäre.
William

Aber ich glaube einfach nicht, dass ich verstehe, was wirklich unter quincenter Konsistenz als "kompositorisch" zu verstehen ist. Ich habe Ihr drittes Beispiel 30 Mal gelesen (und natürlich die Beschreibung), aber nur schwer zu verstehen!
William

Zuerst habe ich einen kleinen Fehler in der Beschreibung des P1∘P2-Beispiels behoben. Aber ich denke, es war Ihnen bereits klar, warum sequentielle Konsistenz (SC) nicht kompositorisch ist. Zweitens ist a = 40 nicht das einzig mögliche Ergebnis im ersten Beispiel [das ein SC-Modell voraussetzt], da A1, A3, B2 legitim sind und zu a = 20 führen. Mein letztes Beispielziel war es, Unterschiede zwischen SC hervorzuheben und Ruhekonsistenz (QC) (res = 1 ist in einem Modell möglich und in dem anderen unmöglich). Ich habe das Thema QC-Kompositionsfähigkeit nicht behandelt: Bitte lesen Sie den Link in der ersten Bearbeitung, um Einblicke in das Thema zu erhalten
Schattenvorlage

@William, ich habe nicht verstanden, warum Sie das Objekt gezwungen haben, keine Ruhephase zwischen add (2) und remove (2) zu haben (indem angenommen wird, dass Thread A einen ausstehenden Methodenaufruf für das Objekt hat). Alternativ wäre es ein Problem, wenn ich Thread A erlauben würde, die Ausführung der add () -Methode abzuschließen, und dann Thread B B2 und dann B1 ausführen würde. Wäre diese Sequenz im Ruhezustand konsistent?
Saurabh Raje
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.