In Multithreading Szenarien erfolgen häufig lesende und schreibende Zugriffe auf Ressourcen, ausgehend von mehreren parallel laufenden Threads. Parallel lesende Zugriffe sind dabei zumeist unkritisch. Threadsicherheit ist aber bei schreibenden Zugriffen zu beachten. Kommen Sperrmechanismen wie lock oder Monitor zum Einsatz, dann erfolgt meist eine Sperre aller mit den Ressourcen arbeitenden Funktionen. Das heisst Lese- und Schreibzugriffe werden gleichzeitig gesperrt.
In Anwendungen in denen sehr viele lesende Zugriffe und nur wenige schreibende Zugriffe auf Ressourcen erfolgen führt dies aber zu einer Verschlechterung der Performance. Für solche Fälle wird ein weiterer Sperrmechanismus in C# angeboten: der ReaderWriterLockSlim.
ReaderWriterLockSlim
Der ReaderWriterLockSlim ist eine geschwindigkeitsoptimierte Weiterentwicklung der ReaderWriterLock Klasse. Er bietet zwei grundlegende Sperrfunktionen:
- Schreibsperre: Erzeugt eine universelle, exklusive Zugriffsperre für die Ressource
- Lesesperre: Erzeugt eine individuelle Zugriffsperre die gleichzeitig mit anderen Lesesperren existieren kann
Das heisst es können mehrere Lesesperren gleichzeitig gesetzt werden. Eine Schreibsperre ist aber immer nur exklusiv möglich und verhindert dabei auch das Setzen von Lesesperren.
Das folgende Beispiel zeigt eine Datenressource die aus fünf Threads heraus genutzt wird. Vier Threads führen lesende Zugriffe aus. Ein Thread greift schreibend auf die Daten zu. Die Verwendung der try-finally Blöcke stellt sicher, dass die Locks auch im Fehlerfall freigegeben werden.
private static int _value; static ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); static void Main(string[] args) { _value = 0; _locker = new ReaderWriterLockSlim(); Parallel.Invoke( Read, Read, Write, Read, Read); Console.ReadKey(); } private static void Read() { while (true) { try { _locker.EnterReadLock(); Console.WriteLine("Read lock enter"); Console.WriteLine("value: " + _value.ToString()); Thread.Sleep(100); Console.WriteLine("Read lock exit"); if (_value > 5) return; } finally { _locker.ExitReadLock(); } } } private static void Write() { while (true) { try { _locker.EnterWriteLock(); Console.WriteLine("Write lock enter"); _value++; Console.WriteLine("Write lock exit"); if (_value > 5) return; } finally { _locker.ExitWriteLock(); } Thread.Sleep(500); } }
Wenn Sie diesen Beispielcode in einer Konsolenanwendung starten sehen Sie, dass mehrere Threads gleichzeitig Leseoperationen ausführen. Beim Schreiben der Daten ist hingegen sichergestellt dass ein exklusiver Zugriff erfolgt.
UpgradeableReadLock
Der ReaderWriterLockSlim bietet noch eine weitere Speerfunktion für aktualisierbare Lesezugriffe. Diese Speerfunktion kann in Szenarien eingesetzt werden bei denen der Schreibzugriff abhängig von einem vorhergehenden Lesezugriff erfolgt.
Dazu ein kleines Beispiel:
Es wird eine Personenliste verwaltet. Wird ein Löschauftrag für eine Person ausgelöst dann soll zuvor geprüft werden ob die Person im Datenbestand existiert und nur dann erfolgt eine Modifikation der Daten. In diesem Fall geht dem Schreibzugriff somit ein Lesezugriff voraus. Nach dem Lesezugriff wird das Resultat geprüft und bei Bedarf erfolgt der Schreibzugriff. Das Problem in diesem Szenario ist, das dies zwei getrennte Befehle sind. Zwischen Lesezugriff und Schreibzugriff kann die Personenliste aus einem anderen Thread heraus modifiziert werden. Dieses Problem lässt sich mittels eines UpgradeableReadLock lösen. Dabei wird ein ReadLock erzeugt das sich in ein WriteLock umwandeln lässt.
Das folgende vereinfachte Beispiel zeigt die Verwendung der entsprechenden Lock-Funktionen für dieses Szenario.
private static void ReadAndWrite() { _locker.EnterUpgradeableReadLock(); if(...) //check if person exists { _locker.EnterWriteLock(); ... //delete person _locker.ExitWriteLock(); } _locker.ExitUpgradeableReadLock(); }
Nested Lock
Verschachtelte Locks sind bei Verwendung des ReaderWriterLockSlim standardmässig nicht erlaubt. Diese lassen sich aber aktivieren indem im Konstruktor LockRecursionPolicy.SupportsRecursion angegeben wird. Bitte beachten Sie, dass bei verschachtelten Sperren bedingt durch die unterschiedlichen Lock-Arten, sehr schnell ein relativ komplexer Ablauf hinsichtlich dem Anfordern und Freigeben von Sperren entstehen kann.
Fazit
Die Klasse ReaderWriterLockSlim bietet einen sehr einfach zu nutzenden Mechanismus für den threadsichern Zugriff auf Daten die selten geändert aber oft abgefragt werden. Der Zugriff erfolgt dabei geschwindigkeitsoptimiert da mehrere parallele Lesezug