Summary: in this tutorial, you will learn how to use the C# ReaderWriterLockSlim
class to control access to a shared resource.
Introduction to C# ReaderWriterLockSlim class
Sometimes, you have multiple threads that read from a shared resource but only have a few threads that write to it. In this case, using the lock
statement may decrease the application’s performance.
The reason is that the lock
statement may block multiple threads from reading the shared resource even though these threads don’t need exclusive access.
The ReaderWriterLockSlim
class allows multiple threads to read from the shared resource simultaneously, but one thread to write to it at a time. Therefore, the ReaderWriterLockSlim
can help improve performance in comparison with the lock
statement.
The ReaderWriterLockSlim
has two kinds of locks:
- Read lock
- Writer lock
The ReaderWriterLockSlim
allows multiple threads to acquire the read lock at the same time, as long as no thread acquired the writer lock.
Also, it only allows a single thread to acquire the write lock at a time and blocks other threads from trying to acquire either the read or write lock.
In practice, the ReaderWriterLockSlim
is useful for scenarios where your applications have multiple readers and fewer writers.
Note that the ReaderWriterLockSlim
incurs some overhead. Therefore, you should use it when the benefits of concurrent access are higher than the cost of acquiring and releasing the lock.
C# ReaderWriterLockSlim example
The following program demonstrates how to use the ReaderWriterLockSlim
object to control concurrent access to a shared variable with five readers and two writers:
using static System.Console;
int counter = 0;
ReaderWriterLockSlim _lock = new();
void Read()
{
while (true)
{
try
{
_lock.EnterReadLock();
WriteLine($"R: Thread {Thread.CurrentThread.ManagedThreadId} is reading: {counter}");
}
finally
{
_lock.ExitReadLock();
}
Thread.Sleep(500);
}
}
void Write()
{
while (true)
{
try
{
_lock.EnterWriteLock();
WriteLine($"W: Thread {Thread.CurrentThread.ManagedThreadId} is writing: {counter++}");
}
finally
{
_lock.ExitWriteLock();
}
Thread.Sleep(2000);
}
}
// create 5 reader threads
for (int i = 0; i < 5; i++)
{
new Thread(() => Read()).Start();
}
// create 2 writer threads
for (int i = 0; i < 2; i++)
{
new Thread(() => Write()).Start();
}
Code language: C# (cs)
How it works.
First, declare an integer variable counter
:
int counter = 0;
Code language: C# (cs)
Second, create a new ReaderWriterLockSlim
object called _lock
:
ReaderWriterLockSlim _lock = new();
Code language: C# (cs)
Third, define the Reader()
method that acquires a read lock on the _lock
object by calling the EnterReadLock()
method in the try
block:
void Read()
{
while (true)
{
try
{
_lock.EnterReadLock();
WriteLine($"R: Thread {Thread.CurrentThread.ManagedThreadId} is reading: {counter}");
}
finally
{
_lock.ExitReadLock();
}
Thread.Sleep(500);
}
}
Code language: C# (cs)
The Read()
method displays the value of the counter
variable.
Also, it releases the read lock on the _lock
object in the finally
block by calling the ExitReadLock()
method.
The Read()
method delays for 500 milliseconds, which is a half of second.
Fourth, define the Write
method that increases the shared variable counter
:
void Write()
{
while (true)
{
try
{
_lock.EnterWriteLock();
WriteLine($"W: Thread {Thread.CurrentThread.ManagedThreadId} is writing: {counter++}");
}
finally
{
_lock.ExitWriteLock();
}
Thread.Sleep(2000);
}
}
Code language: C# (cs)
The Write
method acquires a write lock on the _lock
object in the try
block by calling the EnterWriteLock()
method.
After that, it increases the counter
by one and displays the counter
value to the console. The Write
method releases the write lock in the finally
block by calling the ExitWriteLock()
method.
The Write
method has a delay of 2000 milliseconds, which is 2 seconds.
Fifth, create two reader threads and two writer threads. The reader threads execute the Read
method while the writer threads execute the Write
method.
// create 5 reader threads
for (int i = 0; i < 5; i++)
{
new Thread(() => Read()).Start();
}
// create 2 writer threads
for (int i = 0; i < 2; i++)
{
new Thread(() => Write()).Start();
}
Code language: C# (cs)
Here’s a sample output of the program:
R: Thread 9 is reading: 0
R: Thread 8 is reading: 0
R: Thread 10 is reading: 0
R: Thread 11 is reading: 0
W: Thread 13 is writing: 0
W: Thread 12 is writing: 1
R: Thread 7 is reading: 2
R: Thread 10 is reading: 2
R: Thread 11 is reading: 2
R: Thread 7 is reading: 2
R: Thread 9 is reading: 2
R: Thread 8 is reading: 2
R: Thread 7 is reading: 2
R: Thread 11 is reading: 2
R: Thread 8 is reading: 2
R: Thread 9 is reading: 2
R: Thread 10 is reading: 2
R: Thread 9 is reading: 2
R: Thread 8 is reading: 2
R: Thread 11 is reading: 2
R: Thread 10 is reading: 2
R: Thread 7 is reading: 2
W: Thread 13 is writing: 2
W: Thread 12 is writing: 3
R: Thread 8 is reading: 4
R: Thread 9 is reading: 4
R: Thread 9 is reading: 4
R: Thread 10 is reading: 4
R: Thread 7 is reading: 4
R: Thread 11 is reading: 4
R: Thread 8 is reading: 4
R: Thread 8 is reading: 4
R: Thread 11 is reading: 4
R: Thread 7 is reading: 4
R: Thread 10 is reading: 4
R: Thread 9 is reading: 4
...
Code language: C# (cs)
To terminate the program, you need to close it.
Summary
- Use C#
ReaderWriterLockSlim
to allow multiple threads to read from the shared resource simultaneously, but one thread to write to it at a time.