Synchronization with the Concurrency Runtime - Part 2

In my previous post, I addressed the motivation behind using concurrency runtime’s synchronization primitives and also introduced Critical Section. In this blog, I will cover concurrency runtime’s reader writer lock.

Reader Writer Lock

This class enables multiple threads to read from a shared resource at the same time but only allows one thread to write to it at a time. They share many characteristics with concurrency runtime’s critical section, reader writer locks are non-reentrant and block cooperatively. The reader writer lock resembles the Win32 Slim reader/writer locks (SRWLock). Reader writer lock performs better than critical section in read-mostly environments.

Similarity to Win32 Slim reader/writer locks:

- Can be used only by threads of a single process.

- The reader writer lock can be owned by multiple reader-threads or only one writer thread at any time.

- Non-reentrant.

- Do not support upgrades or downgrades.

Differences with Win32 Slim reader/writer locks:

- The concurrency runtime’s reader writer lock object guarantees that the order of exclusive (writer) lock-ownership is on a first-come, first-serve basis.

- There is no need to explicitly call Initialization of resources before use of the concurrency runtime’s reader writer lock and release after the use of the reader writer lock.

- Cannot specify spin count for the concurrency runtime’s reader writer lock object.

- The concurrency runtime’s reader writer lock enforces cooperative blocking where they yield to other cooperative tasks in the runtime when blocked.

- The concurrency runtime’s reader writer locks give writer preference over readers; i.e. if there are readers and writer(s) simultaneously waiting for the lock, the lock would be handed over to the first writer in queue.

- Exceptions are thrown by the concurrency runtime’s reader writer lock object; on recursive calls, or if unlock is called when the lock is not held, or if a lock is destroyed when being held.

Example:

Given below is a code sample illustrating reader_writer_lock using 4 readers and 1 writer. The readers output the value of the shared data and the writer updates the value of the shared data and outputs it.

 

// reader_writer_lock.cpp

// compile with: /EHsc

#include <ppl.h>

#include <stdio.h>

#include <windows.h>

using namespace std;

using namespace Concurrency;

//number of iterations each thread performs

static const int NUM_ITERATIONS = 2;

//the shared data that needs protection from race/tearing

static unsigned int sharedData = 0;

//Demonstrates the use of the reader lock

void Reader(reader_writer_lock* pRWLock)

{

    for( int i = 0; i < NUM_ITERATIONS; ++i)

    {

        //use the reader lock

        pRWLock->lock_read();

        printf_s("Reading %d\n", sharedData);

        //Sleep for some time, this is to simulate potential work done while holding the lock

        Sleep(100);

        //release the lock

        pRWLock->unlock();

    }

}

//Demonstrates the use of the writer lock

void Writer(reader_writer_lock* pRWLock)

{

    for( int i = 0; i < NUM_ITERATIONS; ++i)

    {

        //use the writer lock

        pRWLock->lock();

        printf_s("\tWriting %d\n", ++sharedData);

        //Sleep for some time, this is to simulate potential work done while holding the lock

        Sleep(100);

        //release the lock

        pRWLock->unlock();

    }

}

int main()

{

    reader_writer_lock rwlock;

    //performs reader writer operations in parallel

    parallel_invoke(

        [&] { Reader(&rwlock); },

        [&] { Reader(&rwlock); },

        [&] { Reader(&rwlock); },

        [&] { Reader(&rwlock); },

        [&] { Writer(&rwlock); }

    );

    return 0;

}

Sample output:

Reading 0

Reading 0

Reading 0

        Writing 1

Reading 1

Reading 1

Reading 1

Reading 1

        Writing 2

Reading 2

An interesting point to note here is that even though we use 4 readers, we notice that only 3 readers output their value and the lock is taken by the writer. This happens because the lock prefers writers. We do not guarantee the order of task execution but if such a guarantee is required, you could consider using events, which I will cover in the next blog post.