2009-07-27 14 views
4

Wie kann ich eine Synchronisationsstruktur wie das erreichen:Verhalten von TMultiReadExclusiveWriteSynchronizer wenn die Förderung der Lesesperre Schreibsperre

Lock.BeginRead 
try 
    if Changed then 
    begin 
    Lock.BeginWrite; 
    try 
     Update; 
    finally 
     Lock.EndWrite; 
    end; 
    // ... do some other stuff ... 
    end; 
finally 
    Lock.EndRead; 
end; 

, ohne die Lesesperre nach dem EndWrite zu verlieren, so dass keine andere Autoren, während diese ausgeführt werden können Codeblock wird ausgeführt.

Wie verhält sich Delphi 2009 TMuliReadExclusiveWriteSynchronizer in diesem Fall?

+1

Ich habe die Frage bearbeitet und eine zusammengefasste Antwort zur Verfügung gestellt, so dass sie für andere nützlich sein könnte. Wenn Sie das nicht glauben, können Sie für das Schließen stimmen. – jpfollenius

Antwort

6

Es scheint, gibt es zwei Kriterien in dieser Frage eingewickelt:

  • „ohne die Lesesperre nach dem EndWrite zu verlieren“
  • „keine andere Autoren können ausgeführt werden, während dieser Code-Block ausgeführt wird“

Ich werde den ersten Punkt nicht weiter adressieren, da andere dies bereits getan haben. Der zweite Punkt ist jedoch sehr heikel und bedarf einer Erklärung.

Zunächst möchte ich sagen, ich beziehe mich auf Delphi 2007. Ich habe keinen Zugang zu 2009. Es ist jedoch unwahrscheinlich, dass das Verhalten, das ich beschreibe, sich geändert hätte.

Der angezeigte Code ermöglicht anderen Autoren, den Wert während des Codeblocks zu ändern. Wenn die Lesesperre zu einer Schreibsperre befördert wird, ist die Lesesperre vorübergehend verloren. Es gibt einen Moment der Zeit, wenn Ihr Thread weder eine Lese- noch eine Schreibsperre hat. Dies ist beabsichtigt, da ansonsten ein Deadlock fast sicher wäre.Wenn der Faden, die eine Lesesperre auf eine Schreibsperre fördert tatsächlich die Lesesperre gehalten während dies zu tun, könnte das folgende Szenario ganz leicht auftreten:

  1. (Gewinde 1) erhalten Lesesperre
  2. (Gewinde 2) erhalten Sperre gelesen (ok, Lesesperre freigegeben ist)
  3. (Gewinde 1) erhalten Schreibsperre (Blöcke; Faden 2 weist eine Lesesperre)
  4. (Gewinde 2) erhalten Schreibsperre (Blöcke; Faden 1 einen Sperre lesen - jetzt blockiert.

Um dies zu verhindern, gibt TMuliReadExclusiveWriteSynchronizer die Lesesperre für einige "Sofortnachrichten" frei, bevor die Schreibsperre erhalten wird.

(Randnotiz: Der Artikel Working with TMultiReadExclusiveWriteSynchronizer auf EDN, in der Sektion "Schließen Sie es Chris, ich bin im Begriff zu ..." scheint fälschlicherweise darauf hinzuweisen, dass das gerade erwähnte Szenario tatsächlich Deadlock wäre. Das hätte sein können geschrieben über eine vorherige Version von Delphi oder es könnte nur falsch sein. Oder ich könnte Missverständnis sein, was es behauptet. Aber schauen Sie sich einige der Kommentare zu dem Artikel.)

Also, nichts mehr über den Kontext vorausgesetzt, Der Code, den Sie gezeigt haben, ist mit ziemlicher Sicherheit falsch. Wenn Sie einen Wert prüfen, während Sie eine Lesesperre haben, und ihn dann zu einer Schreibsperre befördern und annehmen, dass sich der Wert nicht geändert hat, ist dies ein Fehler. Dies ist ein sehr subtiler Haken mit TMuliReadExclusiveWriteSynchronizer.

Hier sind ein paar ausgewählte Teile der Kommentare in der Delphi-Bibliothek Code:

Andere Themen haben die Möglichkeit, auf die geschützte Ressource zu ändern, wenn Sie Beginwrite aufrufen, bevor Sie die Schreibsperre erteilt werden, auch wenn Sie bereits eine Lesesperre geöffnet haben. Die beste Richtlinie besteht nicht darin, Informationen über die geschützte Ressource (z. B. Anzahl oder Größe) über eine Schreibsperre beizubehalten. Erneutes Abrufen von Beispielen der geschützten Ressource nach Erwerben oder Freigeben einer Schreibsperre. Das Funktionsergebnis von BeginWrite gibt an, ob ein anderer Thread die Schreibsperre erhalten hat, während der aktuelle Thread auf die Schreibsperre gewartet hat. Rückgabewert von True bedeutet, dass die Schreibsperre ohne alle dazwischen liegenden Änderungen von anderen Threads erworben wurde. Rückgabewert False bedeutet, dass ein anderer Thread die Schreibsperre erhalten hat, während Sie gewartet haben. Daher sollte die Ressource , die durch das MREWS-Objekt geschützt ist, als geändert betrachtet werden. Alle Proben der geschützten Ressource sollten verworfen werden. Im Allgemeinen ist es besser, immer nur Stichproben der geschützten Ressource nach Erhalt einer Schreibsperre wieder zu erwerben. Das boolesche Ergebnis von BeginWrite und die RevisionsLevel-Eigenschaft helfen Fälle, in denen die erneute Erfassung der Beispiele rechenintensiv oder zeitaufwendig ist.

Hier ist ein Code zum Ausprobieren. Erstellen Sie einen globalen TMultiReadExclusiveWriteSynchronizer namens Lock. Erstellen Sie zwei globale Boolesche Werte: Schlecht und GlobalB. Starten Sie dann eine Instanz jedes dieser Threads und überwachen Sie den Wert von Bad in Ihrem Hauptprogramm-Thread.

type 
    TToggleThread = class(TThread) 
    protected 
    procedure Execute; override; 
    end; 

    TTestThread = class(TThread) 
    protected 
    procedure Execute; override; 
    end; 

{ TToggleThread } 

procedure TToggleThread.Execute; 
begin 
    while not Terminated do 
    begin 
    Lock.BeginWrite; 
    try 
     GlobalB := not GlobalB; 
    finally 
     Lock.EndWrite; 
    end; 
    end; 
end; 

{ TTestThread } 

procedure TTestThread.Execute; 
begin 
    while not Terminated do 
    begin 
    Lock.BeginRead; 
    try 
     if GlobalB then 
     begin 
     Lock.BeginWrite; 
     try 
      if not GlobalB then 
      begin 
      Bad := True; 
      Break; 
      end; 
     finally 
      Lock.EndWrite; 
     end; 
     end; 
    finally 
     Lock.EndRead; 
    end; 
    end; 
end; 

Obwohl es nicht-deterministisch ist, werden Sie wahrscheinlich sehr schnell sehen (weniger als 1 Sekunde), dass der Wert Bad auf True gesetzt wird.Also im Grunde sehen Sie den Wert von GlobalB ist True und dann, wenn Sie es ein zweites Mal überprüfen, ist es False, obwohl beide Überprüfungen zwischen einem BeginRead/EndRead-Paar aufgetreten sind (und der Grund dafür ist, gab es auch ein BeginWrite/EndWrite-Paar) .

Mein persönlicher Rat: Nur nie fördern Sie eine Lesesperre zu einer Schreibsperre. Es ist viel zu einfach, es falsch zu machen. In jedem Fall fördern Sie niemals wirklich eine Lesesperre für eine Schreibsperre (weil Sie vorübergehend die Lesesperre verlieren), also können Sie es auch im Code explizit machen, indem Sie nur EndRead vor BeginWrite aufrufen. Und ja, das bedeutet, dass Sie den Zustand innerhalb des BeginWrite erneut überprüfen müssen. Also im Fall des Codes, den Sie ursprünglich gezeigt haben, würde ich mich überhaupt nicht mit einem Lesesperre beschäftigen. Beginnen Sie einfach mit BeginWrite, weil es Mai entscheiden kann zu schreiben.

+0

+1 Vielen Dank! Ich bin gerade wieder auf dieselbe Frage gestoßen und habe hier Ihre Antwort gefunden, die genau erklärt, was ich beobachtet und vermutet habe. – jpfollenius

4

Erstens: Ihr Code aus EndWrite befindet sich in TSimpleRWSync, einer leichten Implementierung von IReadWriteSync, während TMultiReadExclusiveWriteSynchronizer viel ausgefeilter ist.

Zweitens: Der Aufruf von LeaveCriticalSection (FLock) in EndWrite gibt die Sperre nicht frei, wenn noch einige Aufrufe von EnterCriticalSection (FLock) geöffnet sind (wie die in BeginRead).

Dies bedeutet, dass Ihr Codebeispiel recht gültig ist und erwartungsgemäß funktionieren sollte, wenn Sie eine TSimpleRWSync-Instanz oder eine TMultiReadExclusiveWriteSynchronizer-Instanz verwenden.

+0

+1 Danke! Das sind sehr gute Nachrichten für mich. Ich habe die falsche Implementierung gesucht - habe nur eine Textsuche für EndWrite durchgeführt. Entschuldigung, dass – jpfollenius

3

Ich habe Delphi 2009 nicht, aber ich erwarte, dass es keine Änderungen in der Funktionsweise von TMultiReadExclusiveWriteSynchronizer gab. Ich denke, es ist die richtige Struktur für Ihr Szenario mit einer Bemerkung: Die "BeginWrite" ist eine Funktion, die einen Boolean zurückgibt. Stellen Sie sicher, dass Sie das Ergebnis überprüfen, bevor Sie mit den Schreibvorgängen beginnen.

Auch in Delphi 2006 enthält die TMultiReadExclusiveWriteSynchronizer-Klasse eine Menge Entwicklerkommentare und Debug-Code. Stellen Sie sicher, dass Sie sich die Implementierung ansehen, bevor Sie sie verwenden.

Siehe auch: Working with TMultiReadExclusiveWriteSynchronizer auf EDN

0

Dank der Antworten von Uwe Raabe und Tihauan:

TMultiReadExclusiveWriteSynchronizer funktioniert gut mit einer solchen verschachtelten Verriegelungsstrukturen. Das EndWrite setzt die Lesesperre nicht wieder frei, so dass es leicht möglich ist, eine Lesesperre für eine bestimmte Zeit in eine Schreibsperrung zu promotieren und dann zur Lesesperre zurückzukehren, ohne dass andere Writer eingreifen.