The Simplest Implementation of a Reader-Writer Lock for .NET
An implementation of a basic Reader-Writer lock using only the System.Threading.Monitor class
Introduction
The standard Monitor.Wait
and Monitor.PulseAll
methods are rarely used today, but still could provide powerful capabilities for thread synchronization. As mentioned, we're going to implement a standard Reader-Writer lock, so let's dive into the code straight away:
private readonly object syncRoot = new object();
private readonly object syncWrite = new object();
// A number of acive readers.
private int rcount = 0;
// A total number of pending and active writers.
private int wcount = 0;
That's right, all we need is two counters - one for each owner type and two root objects for synchronization. Next, let's explore the EnterXXXLock sections:
public void EnterReadLock()
{
lock (syncRoot)
{
while (wcount > 0)
{
Monitor.Wait(syncRoot); // Wait till all writers are done.
}
rcount++; // Notify that there is an active reader.
}
}
public void EnterWriteLock()
{
lock (syncRoot)
{
wcount++; // Notify that there is a pending writer.
while (rcount > 0)
{
Monitor.Wait(syncRoot); // Wait till all readers are done.
}
}
Monitor.Enter(syncWrite);
}
The EnterReadLock
method allows the reader to continue only when the resource is not being accessed by writers. The EnterWriteLock
method, in turn, immediately notifies the presence of the writer, and then waits until readers release the lock. Technically, this code splits incoming requests into two large groups of writers and readers, with the former taking precedence. Therefore, we need an extra call to Monitor.Enter(syncWrite)
to ensure that only one writer can access at a given time. The following release logic:
public void ExitReadLock()
{
lock (syncRoot)
{
if (--rcount == 0 && wcount > 0)
{
Monitor.PulseAll(syncRoot); // Notify writers waiting.
}
}
}
public void ExitWriteLock()
{
Monitor.Exit(syncWrite);
lock (syncRoot)
{
if (--wcount == 0)
{
Monitor.PulseAll(syncRoot); // Notify readers waiting.
}
}
}
The last reader or writer in a row just wakes up all waiting threads so they can continue the race. To demonstrate how this works, I've created a console application that runs multiple threads accessing the same resource at the same time. That's the sample output generated by the app:
The source code is available for download at this link. Just un-zip it and execute the dotnet run
command.
History
- 23rd January, 2022: Initial version