C# ReaderWriterLockSlim Class
In C#, the ReaderWriterLockSlim class is present in the System.Threading namespace which is used to provide synchronization in a multithreaded environment. It allows multiple threads to read the resource concurrently, but only one thread can write at a time
Key Features:
- More flexibility and optimization than the traditional ReaderWriterLock.
- It is easy to use with recursion, simplifies the lock for reading or writing multiple times, and handles the possible deadlock conditions internally.
- Provide an easier mechanism for upgrading and downgrading Locks.
Example: Use of ReaderWriterLockSlim to update the shared resource in a multithreaded environment.
// Using ReaderWriterLockSlim in C#
using System;
using System.Threading;
class Geeks
{
private static ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
private static string res = "Initial Value";
public static void Main()
{
Thread write = new Thread(Writer);
Thread read1 = new Thread(Reader);
Thread read = new Thread(Reader);
write.Start();
read1.Start();
read.Start();
write.Join();
read1.Join();
read.Join();
}
private static void Writer()
{
rwLock.EnterWriteLock();
try
{
res = "Value is updated";
Thread.Sleep(200);
Console.WriteLine("Performed written operation.");
}
finally
{
rwLock.ExitWriteLock();
}
}
private static void Reader()
{
rwLock.EnterReadLock();
try
{
Console.WriteLine("Shared Resource: " + res);
Thread.Sleep(100);
}
finally
{
rwLock.ExitReadLock();
}
}
}
Output
Performed written operation. Shared Resource: Value is updated Shared Resource: Value is updated
Explanation: In the above example, we use ReaderWriterLockSlim to perform read and write operations on the shared resource. We create two threads which perform the reading operation and one thread is performing the writing operation on the shared resource.
Constructor
Constructor | Description |
---|---|
ReaderWriterLockSlim() | This constructor creates a new instance of the |
ReaderWriterLockSlim(LockRecursionPolicy recursionPolicy) | This constructor creates a new |
Example: Initializing the ReaderWriterLockSlim with the ReaderWriterLockSlim(LockRecursionPolicy) constructor.
// Initializing the ReaderWriterLockslim
// With the ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion)
using System;
using System.Threading;
public class Geeks
{
// Create a ReaderWriterLockSlim with recursion policy set to SupportsRecursion
private static ReaderWriterLockSlim Lock =
new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
// Shared resource to be read and written
// Initial value is set to 0
private static int c = 0;
// Method to read the shared resource
public static void ReadResource()
{
Lock.EnterReadLock();
try
{
Console.WriteLine("Reading resource: " + c);
}
finally
{
Lock.ExitReadLock();
}
}
// Method to write to the shared resource
public static void WriteResource(int newValue)
{
Lock.EnterWriteLock();
try
{
Console.WriteLine("Writing resource: " + newValue);
c = newValue;
}
finally
{
Lock.ExitWriteLock();
}
}
// Reader method that calls itself recursively
public static void RecursiveRead(int count)
{
if (count > 0)
{
Lock.EnterReadLock();
try
{
Console.WriteLine($"Recursively reading resource, count = {count}: " + c);
// Call the method recursively
RecursiveRead(count - 1);
}
finally
{
Lock.ExitReadLock();
}
}
}
public static void Main()
{
// Writing initial value to shared resource
WriteResource(100);
// Start multiple threads to simulate reading and recursion
Thread read1 = new Thread(() => ReadResource());
Thread read2 = new Thread(() => RecursiveRead(2));
read1.Start();
read2.Start();
read1.Join();
read2.Join();
// Writing new value to
// shared resource
WriteResource(200);
}
}
Output
Writing resource: 100 Reading resource: 100 Recursively reading resource, count = 2: 100 Recursively reading resource, count = 1: 100 Writing resource: 200
Explanation: In the above example, we initialize a ReaderWriterLockSlim with the constructor ReaderWriterLockSlim(LockRecursionPolicy recursionPolicy) which allows to use of lock recursively here we create a thread which is used to read the shared resource recursively.
Properties
Property | Description |
---|---|
IsReadLockHeld | This property checks whether the current thread holds a read lock or not. |
IsUpgradeableReadLockHeld | It is used to check whether the current thread holds an upgradeable read lock or not. |
IsWriteLockHeld | It is used to check the current thread holds a write lock. |
RecursionPolicy | It is used to retrieve the recursion policy for the lock And check the lock can be reacquired recursively. |
CurrentReadCount | Used to retrieve the number of threads that presently hold a read lock. |
RecursiveReadCount | It returns the number of times the current thread has entered the read lock in recursion. |
RecursiveUpgradeCount | This property is used to retrieve the number of times the current thread has entered the upgradeable read lock in recursive mode. |
RecursiveWriteCount | It is used to retrieve the number of times the current thread has entered the write lock in recursive mode. |
WaitingReadCount | Used to retrieve the number of threads which are currently waiting to enter the read lock. |
WaitingUpgradeCount | This property retrieves the number of threads which are currently waiting to enter the upgradeable read lock. |
WaitingWriteCount | This property is used to check the count of the number of threads that are waiting to enter the write lock. |
Example: Using the IsReaderLockHeld and IsWriterLockHeld properties to check the lock's status during execution.
using System;
using System.Threading;
class Geeks
{
// Shared resource
private static int val = 0;
// Creating the ReaderWriterLockSlim object
private static ReaderWriterLockSlim rwLock =
new ReaderWriterLockSlim();
// Read operation
public static void Read()
{
// Entering the read lock
rwLock.EnterReadLock();
try
{
// Accessing shared resource (counter)
Console.WriteLine($"Reader Thread {Thread.CurrentThread.ManagedThreadId} reading counter: {val}");
}
finally
{
// Releasing the read lock
rwLock.ExitReadLock();
}
}
// Write operation
public static void Write(int value)
{
// Entering the write lock (exclusive access)
rwLock.EnterWriteLock();
try
{
// Modifying shared resource (val)
val = value;
Console.WriteLine($"Writer Thread {Thread.CurrentThread.ManagedThreadId} updated counter to: {val}");
}
finally
{
// Releasing the write lock
rwLock.ExitWriteLock();
}
}
public static void Main()
{
// Creating reader threads
Thread read = new Thread(Read);
Thread read2 = new Thread(Read);
Thread read3 = new Thread(Read);
// Creating writer thread
Thread write = new Thread(() => Write(3));
// Starting threads
read.Start();
read2.Start();
read3.Start();
write.Start();
// Waiting for all threads to finish
read.Join();
read2.Join();
read3.Join();
write.Join();
// Checking lock status after the operations
Console.WriteLine($"IsReaderLockHeld: {rwLock.IsReadLockHeld}");
Console.WriteLine($"IsWriterLockHeld: {rwLock.IsWriteLockHeld}");
}
}
Output
Writer Thread 6 updated counter to: 3 Reader Thread 4 reading counter: 3 Reader Thread 5 reading counter: 3 Reader Thread 3 reading counter: 3 IsReaderLockHeld: False IsWriterLockHeld: False
Explanation: In the above example, we use the ReadWriterLockSlim class properties like IsReaderLockHeld which is used to check whether the status of the lock is completed or not, the output may be different each time because we are performing the multithreaded operations.
Methods
Method | Description |
---|---|
EnterReadLock() | This method is used to read the lock or block if necessary until the lock is available for the required. |
TryEnterReadLock(TimeSpan timeout) | This method tries to acquire a read lock and we can specify a timeout period in the argument. |
TryEnterReadLock(int millisecondsTimeout) | It is used to try to acquire a read lock within we can also specify the timeout timings in the argument in milliseconds. |
EnterWriteLock() | This method takes a write lock, blocking if necessary until the lock is available. |
TryEnterWriteLock(TimeSpan timeout) | This method acquires a write lock within the specified timeout period which we passed on the argument in milliseconds. If the lock cannot be acquired within the given time, it returns false. |
TryEnterWriteLock(int millisecondsTimeout) | It tries to acquire a write lock in the specified timeout period in the argument in milliseconds. If the lock cannot be acquired within the given time, it returns false. |
EnterUpgradeableReadLock() | This method takes control over the upgradeable read lock, Awaiting the lock acquisition. |
TryEnterUpgradeableReadLock(TimeSpan timeout) | This method is used to take control over an upgradeable read lock within the specified timeout period. |
TryEnterUpgradeableReadLock(int millisecondsTimeout) | Use to take control over an upgradeable read lock within the specified timeout period in milliseconds. If the lock cannot be acquired within the given time, it returns false. |
ExitReadLock() | This method is used to free a read lock held by the current thread. |
ExitWriteLock() | It is used to free a write lock which is held by the current thread. |
ExitUpgradeableReadLock() | This method is used to release the upgradeable read lock held by the current thread. |
Dispose() | It is used to free all resources used when the lock is no longer needed, to ensure that system resources are freed up. |
Example: Using EnterReadLock(), TryEnterReadLock(int millisecondsTimeout), and EnterWriteLock() from the ReaderWriterLockSlim class.
// Using methods of ReaderWriterLockSlim class in C#
using System;
using System.Threading;
class Geeks
{
// Shared resource
private static int res = 0;
// Create ReaderWriterLockSlim instance
private static ReaderWriterLockSlim rwLock =
new ReaderWriterLockSlim();
// Method for reading
public static void Read()
{
try
{
// Acquiring a read lock
rwLock.EnterReadLock();
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} reading counter: {res}");
}
finally
{
// Releasing the read lock
rwLock.ExitReadLock();
}
}
// Method for trying to acquire read lock
// With a timeout (using TryEnterReadLock)
public static void TryReadWithTimeout()
{
// Attempting to acquire the read lock with 500ms timeout
if (rwLock.TryEnterReadLock(500))
{
try
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} successfully acquired read lock.");
Console.WriteLine($"Counter value: {res}");
}
finally
{
rwLock.ExitReadLock();
}
}
else
{
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} could not acquire read lock within timeout.");
}
}
// Method for writing (using EnterWriteLock)
public static void Write()
{
try
{
// Acquiring a write lock
rwLock.EnterWriteLock();
res++;
Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId} updated counter to: {res}");
}
finally
{
// Releasing the write lock
rwLock.ExitWriteLock();
}
}
public static void Main()
{
// Create and start threads for reading and writing
Thread read1 = new Thread(Read);
Thread read2 = new Thread(TryReadWithTimeout);
Thread write = new Thread(Write);
read1.Start();
read2.Start();
write.Start();
read1.Join();
read2.Join();
write.Join();
}
}
Output
Thread 5 updated counter to: 1 Thread 3 reading counter: 1 Thread 4 successfully acquired read lock. Counter value: 1
Explanation: In the above example, we use EnterReadLock() method blocks until the read lock is available. It acquires the lock for the specified time which we pass 500 milliseconds and if it does not acquire the lock on the specified time then it will show the error message. And read the shared resource then we use the EnterWriteLock() which acquires the write lock updates the shared resource and then releases the lock.
Note: ReaderWriterLockSlim is not thread-abort safe. If threads accessing it can be aborted. The abort method is also obsoluted in the newer .NET versions.