<p>If you want to avoid <strong>Writer starvation</strong> then you could consider another algorithm. I research some algorithms, which avoid the starvation of the Writer problem (e.g. in this <a href="https://arxiv.org/pdf/1309.4507" rel="nofollow noreferrer">paper</a>). One of the solution proposal pseudo-code is the following: <a href="https://i.stack.imgur.com/Tw67R.png" rel="nofollow noreferrer">pseudo-code image</a>.</p>
<pre><code>public class ReadWriterSynchronizer : IDisposable
{
    public ReadWriterSynchronizer(string name, int maxReaderCount)
    {
        myIncomingOperation = new Semaphore(1, 1, name + &quot;.Incoming&quot;);
        myReadOperation = new Semaphore(1, 1, name + &quot;.Reader&quot;);
        myWriteOperation = new Semaphore(1, 1, name + &quot;.Writer&quot;);
        myCrossprocessCounter = new ReaderCounter(name + &quot;.Counter&quot;, maxReaderCount);
    }

    public void EnterReadLock()
    {
        myIncomingOperation.WaitOne();
        myReadOperation.WaitOne();

        // Local variable is necessary, because of optimalization
        int currentCount = myCrossprocessCounter.Increase();
        if (currentCount == 1)
        {
            myWriteOperation.WaitOne();
        }

        myReadOperation.Release();
        myIncomingOperation.Release();
    }

    public void ExitReadLock()
    {
        myReadOperation.WaitOne();

        // Local variable is necessary, because of optimalization
        int currentCount = myCrossprocessCounter.Decrease();
        if (currentCount == 0)
        {
            myWriteOperation.Release();
        }

        myReadOperation.Release();
    }

    public void EnterWriteLock()
    {
        myIncomingOperation.WaitOne();
        myWriteOperation.WaitOne();
    }

    public void ExitWriteLock()
    {
        myWriteOperation.Release();
        myIncomingOperation.Release();
    }

    public void Dispose()
    {
        myIncomingOperation?.Dispose();
        myReadOperation?.Dispose();
        myWriteOperation?.Dispose();
        myCrossprocessCounter?.Dispose();

        GC.SuppressFinalize(this);
    }

    private readonly ReaderCounter myCrossprocessCounter;
    private readonly Semaphore myIncomingOperation;
    private readonly Semaphore myReadOperation;
    private readonly Semaphore myWriteOperation;
}
</code></pre>
<p>Unfortunately, <code>ctr</code> variable is an integer, therefore it could only work in interprocess scenarios. I decided to replace the integer counter with a <strong>Semaphore counter</strong> (<code>ReaderCounter</code>) so it could be used for cross-process communication. Essentially, I used <code>WaitOne(0)</code> in order to <strong>decrease</strong> and <code>Release()</code> to <strong>increase</strong> the reader counter.</p>
<pre><code>internal class ReaderCounter : IDisposable
{
    internal ReaderCounter(string name, int maxConcurrentRead)
    {
        MaximumCount = maxConcurrentRead + InitialCount;
        myReadCounterSemaphore = new Semaphore(InitialCount, MaximumCount, name);
        myIncomingOperation = new Semaphore(1, 1, name + &quot;.Incoming&quot;);
    }

    internal int Increase()
    {
        int counter = RetrieveCurrentCount();

        // Not allowing to exceed maximum count
        if (counter != MaximumCount - 1)
        {
            counter = myReadCounterSemaphore.Release();
        }
        else
        {
            counter++;
        }

        return counter;
    }

    internal int Decrease()
    {
        int counter = RetrieveCurrentCount() - 1;
        myReadCounterSemaphore.WaitOne(0);

        return counter;
    }

    public void Dispose()
    {
        myReadCounterSemaphore?.Dispose();
        myIncomingOperation?.Dispose();

        GC.SuppressFinalize(this);
    }

    internal int MaximumCount { get; private set; }

    private const int InitialCount = 1;
    private readonly Semaphore myReadCounterSemaphore;
    private readonly Semaphore myIncomingOperation;

    private int RetrieveCurrentCount()
    {
        myReadCounterSemaphore.WaitOne(0);
        int counter = myReadCounterSemaphore.Release();
        return counter;
    }
}
</code></pre>
<p>NOTE: For easier usage, 1 puffer count was added to the reader counter. For example, using a 5 reader means [1,6] initial Semaphore count. Decreasing from the minimum count returns with -1 and Increasing from maximum count return with maximum count +1.</p>
<p><strong>UPDATE</strong>: I have created a GitHub repository with console applications, so you can play with it. It also contains ReaderWriterSynchronizer with <code>TryEnterReadLock()</code> and <code>TryEnterWriteLock()</code> methods: <a href="https://github.com/SzilvasiPeter/Cross-process-ReaderWriterLock" rel="nofollow noreferrer">https://github.com/SzilvasiPeter/Cross-process-ReaderWriterLock</a></p>
Cross-process read-write synchronization primative in .NET?
<p>Is there a read/write locking mechanism that works across processes (similar to Mutex, but read/write instead exclusive locking)?  I would like to allow concurrent read access, but exclusive write access.</p>