0

For sake of practice, I am trying to write a solution to the readers-writers problem.
The expected behavior should be that multiple reads can run concurrently, but writes need to wait for all readers to finish.
My solution is below in Read(), Write() methods, and the book I am referencing suggests Write2() for the writers.

1) I don't entirely understand why they chose to implement this way, specifically why the read lock is trying to be acquired again, after being awoken when numOfReaders == 0. Is that to give readers priority, if one came right after Write acquired the read lock, and right before it actually wrote anything?

2) Are there any other issues with the my suggested Write implementation? Thanks!!

class ReaderWriter
{
    private int numOfReaders = 0;
    private readonly object readLock = new object();
    private readonly object writeLock = new object();

    public void Read()
    {
        lock (readLock)
        {
            this.numOfReaders++;
        }

        // Read stuff

        lock (readLock)
        {
            this.numOfReaders--;
            Monitor.Pulse(readLock);
        }
    }

    // My solution
    public void Write()
    {
        lock (writeLock)
        {
            lock (readLock)
            {
                while (this.numOfReaders > 0)
                {
                    Monitor.Wait(readLock);
                }

                // Write stuff
            }
        }
    }

    // Alternative solution
    public void Write2()
    {
        lock (writeLock)
        {
            bool done = false;
            while (!done)
            {
                lock (readLock)
                {
                    if (this.numOfReaders == 0)
                    {
                        // Write stuff
                        done = true;
                    }
                    else
                    {
                        while (this.numOfReaders > 0)
                        {
                            Monitor.Wait(readLock);
                        }
                    }
                }
            }
        }
    }
}
Itsik
  • 3,920
  • 1
  • 25
  • 37
  • Well, your solution has lots of bottlenecks. Your read locks, having a mutex, are as contended as writes. – Jazzwave06 Oct 16 '17 at 02:05
  • Assuming read is a lengthy operation, that overhead should be minimal, as it is only incrementing an integer. How otherwise would you block readers, when a writer is writing ? – Itsik Oct 16 '17 at 02:06
  • Obtaining a lock is a lengthy operation. – Jazzwave06 Oct 16 '17 at 02:06
  • True, but that depends on the application. It is typically less than 0.5us. Relative to I/O, it is neglible – Itsik Oct 16 '17 at 02:08
  • You rarely use a reader writer to manage IO. Sockets are handled on a single thread (i.e event loops). Files can be written synchronously and moved atomically to their destination. Databases all handle their locks autonomously. Locks are used to synchronize memory operations, which are very fast compared to obtaining the lock. Reader writer locks are used to minimize read contention. Your implementation has lot of that. Just sayin'. – Jazzwave06 Oct 16 '17 at 02:10
  • https://gist.github.com/jboner/2841832 Obtaining a lock is 4x faster than accessing memory. (I was wrong in my previous quote. It is 25ns). Regardless, how otherwise would you suggest blocking readers when a thread is writing? – Itsik Oct 16 '17 at 02:12
  • @MickyD but then you allow one reader at a time. How does that solve the problem? – Itsik Oct 16 '17 at 02:15
  • I doubt those numbers. Mutexes are OS objects, and require a system call to be locked and unlocked. Nevertheless, there's a case where read should not lock anything: nobody requested the write lock, and you (and possibly others) want a read lock. This case should lock nothing and be optimized for massive reads. – Jazzwave06 Oct 16 '17 at 02:17
  • @sturcotte06 Are you doubting numbers published of Jeff Dean ? – Itsik Oct 16 '17 at 02:20
  • @MickyD that is a good point, you could solve that easily with a finally block. This was not intended to be production code – Itsik Oct 16 '17 at 02:20
  • I'm doubting which mutexes he used to produce those numbers. In pthread, locking a pthread_mutex_t involves a system call that accesses memory and edits memory, which may or may not send the thread into the wait/sleep/join state (involves the os scheduler). This is necessarily longer than accessing memory. The 25ns seems to come from transactional memory mutexes; I doubt that normal C# monitor use transactional memory (I might be wrong). Nevertheless, it is 25ns when there's no contention whatsoever. – Jazzwave06 Oct 16 '17 at 15:31
  • Why you just don't use a default implementation? are there any advantages of your's solution? – VMAtm Oct 17 '17 at 00:22
  • @VMAtm no advantages. Just an attempt to implement functionality similar to the built-in ReaderWriterLock – Itsik Oct 17 '17 at 08:11
  • Built-in class, as far as I know, do not use locking - only `Interlocked` class usage – VMAtm Oct 17 '17 at 08:30

0 Answers0