Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

The Simplest Implementation of a Reader-Writer Lock for .NET

5.00/5 (4 votes)
24 Jan 2022CPOL1 min read 12.3K   169  
An implementation of a basic Reader-Writer lock using only the System.Threading.Monitor class
The article shows how you can create a simple synchronization primitive that restricts access to a shared resource to a single writer, while still allowing multiple readers to access it simultaneously. Of course, it's not supposed to replace the highly optimized default ReaderWriterLockSlim implementation, but nonetheless, it's a very interesting demonstration of how Monitor.Wait and Monitor.PulseAll can be used to achieve the goal.

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:

C#
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:

C#
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:

C#
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)