Wie verhindert man das Blockieren von ReaderWriterLockSlim-Lesern, wenn der Writer versucht, die Schreibsperre einzugeben?

8

Ich verwende ReaderWriterLockSlim , um einige Operationen zu schützen. Ich möchte Leser gegenüber Autoren bevorzugen, so dass, wenn ein Leser das Schloss lange hält und ein Schreiber versucht, die Schreibsperre zu erlangen, weitere Leser auf der Straße nicht vom Versuch des Schreibers blockiert werden (was stattdessen passieren würde, wenn der Schreiber wurde auf lock.EnterWriteLock() ) blockiert.

Zu diesem Zweck habe ich mir gedacht, dass der Schreiber TryEnterWriteLock mit einem kurzen Timeout in einer Schleife verwenden könnte, so dass nachfolgende Leser immer noch die Lesesperre erhalten könnten, während der Schreiber dies nicht kann. Zu meiner Überraschung stellte ich jedoch fest, dass ein fehlgeschlagener Aufruf von TryEnterWriteLock den Zustand der Sperre änderte und zukünftige Leser ohnehin blockierte. Konzeptnachweis:

%Vor%

Die Ausgabe dieses Codes lautet:

%Vor%

Wie Sie sehen können, obwohl Thread 2 (der "Writer") keine Schreibsperre erhalten hat und sich nicht in einem EnterWriteLock -Aufruf befindet, wird Thread 3 für immer blockiert. Ich kann ein ähnliches Verhalten mit ReaderWriterLock sehen.

Mache ich etwas falsch? Wenn nicht, welche Möglichkeiten habe ich, Leser zu bevorzugen, wenn ein Schreiber in der Warteschlange steht?

    
Gabriele Giuseppini 18.09.2015, 14:38
quelle

2 Antworten

3

Ich kann nicht helfen, aber ich glaube, das ist ein .NET Framework Bug (UPDATE: Ich habe den Fehler gemeldet ). Das folgende einfache Programm (eine vereinfachte Version des Obigen) veranschaulicht das:

%Vor%

Das Folgende passiert in einer einfachen Reihenfolge, keine Konvois, keine Rennen, keine Schleifen oder irgendetwas.

  1. T1 erwirbt eine Lesesperre.
  2. T2 versucht, eine Schreibsperre zu erhalten und blockiert, wobei auf eine Zeitüberschreitung gewartet wird (wie T1 die Sperre hält).
  3. T3 versucht, eine Lesesperre zu erhalten und blockiert (weil T2 blockiert ist und auf die Schreibsperre wartet, und nach der Dokumentation bedeutet dies, dass alle weiteren Leser bis zu Timeouts blockiert sind).
  4. Das Timeout von T2 läuft ab. Gemäß der Dokumentation sollte T3 nun aufwachen und die Lesesperre erhalten. Dies geschieht jedoch nicht und T3 wird für immer blockiert (oder im Fall dieses Beispiels für die 50 Sekunden, bis T1 die Lesesperre verlässt).

AFAICT, die ExitMyLock in ReaderWriterLockSlim 's WaitOnEvent sollte ExitAndWakeUpAppropriateWaiters sein.

    
Mormegil 18.09.2015 16:03
quelle
2

Ich stimme der Antwort von Mormegil zu, dass Ihr beobachtetes Verhalten anscheinend nicht mit der Dokumentation für ReaderWriterLockSlim.TryEnterWriteLock-Methode (Int32) gibt an:

  

Während Threads blockiert sind, die auf den Schreibmodus warten, versuchen zusätzliche Threads, in den Lesemodus oder in den aktualisierbaren Modus zu wechseln, bis alle Threads, die auf den Schreibmodus warten, entweder abgelaufen oder in den Schreibmodus versetzt wurden. p>

Beachten Sie, dass es zwei dokumentierte Möglichkeiten gibt, dass andere Threads, die auf den Lesemodus warten, nicht mehr warten:

  • TryEnterWriteLock überschreitet das Zeitlimit.
  • TryEnterWriteLock ist erfolgreich und gibt dann die Sperre durch Aufrufen von ExitWriteLock .
  • frei

Das zweite Szenario funktioniert wie erwartet. Aber das 1. (das Timeout-Szenario) funktioniert nicht, wie Ihr Codebeispiel deutlich zeigt.

Warum funktioniert das nicht?

Warum warten Threads, die versuchen, in den Lesemodus zu wechseln, auch nach einem Zeitüberschreitungsversuch in den Schreibmodus?

Damit wartende Threads in den Lesemodus wechseln können, müssen zwei Dinge geschehen. Der Thread, der das Warten auf eine Schreibsperre abwartet, muss:

Oben steht sollte vorkommen. In der Praxis wird jedoch, wenn TryEnterWriteLock abgelaufen ist, nur der erste Schritt ausgeführt (Zurücksetzen des Flags), aber nicht der zweite (Signalisierung wartender Threads, um das Erfassen der Sperre erneut zu versuchen). In diesem Fall bleibt der Thread, der versucht, die Lesesperre zu erhalten, auf unbestimmte Zeit zu warten, weil er nie "erfahren" wird, dass er aufwachen und die Markierung erneut prüfen soll.

Wie von Mormegil hervorgehoben, ändern Sie den Aufruf von ExitMyLock() in ExitAndWakeUpAppropriateWaiters() in diese Codezeile scheint alles zu sein, was zur Behebung des Problems benötigt wird. Da die Lösung so einfach erscheint, bin ich auch geneigt zu denken, dass dies nur ein Bug ist, der übersehen wurde.

Wie ist diese Information nützlich?

Wenn wir die Ursache verstehen, erkennen wir, dass das "Buggy" -Verhalten etwas eingeschränkt ist. Es bewirkt nur, dass Threads "unbegrenzt" blockieren, wenn sie versuchen, die Sperre zu übernehmen. Nachdem ein Thread TryEnterWriteLock aufruft, vor Der Timeout von TryEnterWriteLock wird überschritten. Und es blockiert nicht wirklich unendlich . Es wird schließlich entsperrt, sobald ein anderer Thread seine Sperre normal freigibt, was in diesem Fall das erwartete Szenario wäre.

Dies bedeutet auch, dass jeder Thread, der versucht, in den READ-Modus nach TryEnterWriteLock zu gelangen, dies ohne Probleme tun kann.

Um dies zu verdeutlichen, führen Sie das folgende Code-Snippet aus:

%Vor%

Ausgabe:

  

00000: T1 versucht in den READ-Modus zu gelangen ...
  00001: T1 ist erfolgreich in den READ-Modus gegangen!
  01011: T2 versucht in den WRITE-Modus zu gelangen ...
  02010: T3 versucht in den READ-Modus zu gelangen ...
  03010: T2 hat abgelaufen! Der Schreibmodus konnte nicht aufgerufen werden.
  04013: T4 versucht in den READ-Modus zu gelangen ...
  04013: T4 ist erfolgreich in den READ-Modus gegangen! War nicht betroffen von T2 Timeout "Bug"
  04013: T4 beendet den READ-Modus. (Nebeneffekt: weckt alle anderen Kellner-Threads auf)   04013: T3 ist immerhin in den READ-Modus gegangen! T4's ExitReadLock () entsperrt mich.
  04013: T3 hat den READ-Modus verlassen.
  10005: T1 hat den READ-Modus verlassen.

Beachten Sie auch, dass der T4 Reader-Thread nicht unbedingt erforderlich ist, um T3 zu entsperren. T1 's eventual ExitReadLock hätte auch T3 entsperrt.

Letzte Gedanken

Obwohl das aktuelle Verhalten weniger als ideal ist und sich wie ein Fehler in der .NET-Bibliothek anfühlt, wurden in einem realen Szenario die Leser-Threads, die den Writer-Thread verursachten, an erster Stelle auf Timeout gesetzt würde schließlich den READ-Modus verlassen. Und das wiederum würde alle wartenden Reader-Threads entsperren, die wegen des "Bugs" hängen bleiben könnten. Die Auswirkungen des "Fehlers" auf das wirkliche Leben sollten daher minimal sein.

Dennoch, wie in den Kommentaren vorgeschlagen, um Ihren Code noch zuverlässiger zu machen, wäre es keine schlechte Idee, Ihren EnterReadLock() -Aufruf in TryEnterReadLock mit einem angemessenen Timeout-Wert zu ändern und innerhalb einer Schleife aufzurufen.Auf diese Weise können Sie ein gewisses Maß an Kontrolle über die maximale Zeit haben, die ein Leser-Thread "hängen bleibt".

    
sstan 21.09.2015 13:36
quelle