Exercise 3: Working with the reader_writer_lock

Figure 1

In this exercise you will spawn various threads that will read from a shared variable. The code will also create a thread that will write to the shared data. As you will see, sometimes there will be inconsistencies when a writer thread changes a variable at the same time that thread has just read from it.

You will then use a reader_writer_lock to synchronize access so that no data races occur while allowing multiple readers to simultaneously read from the shared data.

Part 1: Running the Unsynchronized Code

  1. Open the Exercise-3solution file found at the following folder:

    C:\Server 2008 R2 Labs\Working with the CRT\Exercise-3\

  2. From the Solution Explorer, double click the Exercise-3.cpp file under the Sources folder.
  3. Scroll down to main and focus on the parallel_invoke call:

    C++

    parallel_invoke( [&] { Reader(); }, [&] { Reader(); }, [&] { Reader(); }, [&] { Reader(); }, [&] { Writer(); } );
    Note:
    In this example, we will simulate a reader and writer scenario. We’ll spawn 4 reader threads and 1 writer thread. The Reader threads will iterate in a loop and read the value from a shared variable. The Writer thread will also iterate and will write to the same shared variable.

  4. Scroll up and examine the bodies of the Reader() and Write()methods.

    Note:
    The Reader method simply reads from the shared Datavariable and writes the fetched value on screen. TheWriter method increments the variable and writes to screen when it did.

  5. Before we start the program, scroll to the top of the file, you should see the NUM_ITERATIONS constant, which is used to specify how many the readers and writers will read and write, respectively, the variable:

    C++

    static const int NUM_ITERATIONS = 2;

  6. From the Debug menu, select Start without Debugging(click Yes if prompted to build the project)

    Note:
    If you have more than one processor/core in your machine, then you might see the scenario below (try running the programs several times until you can see the data race condition):

    Figure 2

    Note:
    The readers only want to read from the resource, the writer wants to write to it. Obviously, there is no problem if two or more readers access the resource simultaneously. However, if a writer and a reader or two writers access the resource simultaneously, the result becomes indeterminable (which is the case you see above). Therefore the writers must have exclusive access to the resource.

  7. Press any key to close the program’s window and return to Microsoft Visual Studio 2010.

Part 2: Synchronization using the reader_writer_lock

  1. Back in Visual Studio, scroll down to the main function.

    Note:
    In order to synchronize data access between the threads, we could use a critical_section, but that would block all readers from reading the shared variable when one Reader is reading from it, which would be a waste. To circumvent this problem, we’ll use a different kind of synchronization primitive.

  2. Declare the following reader_writer_lock variable as the first line of the main function:

    C++

    reader_writer_lock rwlock;
    Note:
    This reader_writer_lock 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.

  3. Send the lock as a parameter to the functions Reader and Writer by making the following modifications to the parallel_invoke function:

    C++

    parallel_invoke(
    FakePre-e95f28b3618e4096aac5428d37cc444d-e69b2923bbb94404a4b6e68eb06d3ea9FakePre-6856f6490bad40a6bbedc8a417e42b79-4f1711ea20bd43e7a3f7340ac28a6c1aFakePre-cd2fde9b98174d0aabbfe6adef21fdf2-ec8d99ce411944218b048f336f48c868FakePre-7e12afb5d2d54692bfdaef7a2d5de3d1-154f0ddba224448f9a503f4613d68fedFakePre-1d3dff6ec01a4ffdaf5b664f3c2b34b4-4b1e16dfe46f4d46b35320aa8e190fdaFakePre-183510e120594599bca3289efda99257-81c24a4138af44ecbc96b0506e2e3624FakePre-6bfda515f81142b383c4cae99edee57b-9e517fad4faa47b99583a10a72ae536a

  4. Scroll up until you see the Reader method.
  5. Change the method’s declaration to specify that it requires an incoming parameter of type reader_writer_lock:

    C++

    void Reader(reader_writer_lock* pRWLock)

  6. Inside the Reader method’s for-loop,obtain the lock that will guarantee that the value of the shareData value won’t be modified when it’s being read and will not block other threads from reading it:

    C++

    pRWLock->lock_read();

  7. Once the variable is read, we must release the lock to allow other writer threads to change it if needed. Release the lock by typing the following code below the line where the Sleep method is invoked:

    C++

    pRWLock->unlock();

  8. Scroll down to the Writer function.
  9. Change the method’s declaration to specify that it requires an incoming parameter of type reader_writer_lock:

    C++

    void Writer(reader_writer_lock* pRWLock)

  10. Inside the Writer method’sfor-loop, capture the lock that will prevent other readers (and writers, if there were any) to read the lock:

    C++

    pRWLock->lock();

  11. Once the variable is written to, we must release the lock to allow other reader/writer threads to read/write to it (respectively) if needed. Release the lock by typing the following code below the line where the Sleep method is invoked:

    C++

    pRWLock->unlock();

  12. From the Debug menu, select Start Without Debugging(click Yes if prompted to build the project)
Note:
You should now see that there are no data race conditions between the writer thread and the reader threads:

Figure 3

Note:
An interesting point to note here is that even though we use 4 readers, we notice that only 2 readers output their value and the writer takes the lock. 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 (next exercise).