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:
- (Gewinde 1) erhalten Lesesperre
- (Gewinde 2) erhalten Sperre gelesen (ok, Lesesperre freigegeben ist)
- (Gewinde 1) erhalten Schreibsperre (Blöcke; Faden 2 weist eine Lesesperre)
- (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.
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