Exercise 2: Working with the critical_section class

Figure 1

By now, you should be aware that shared variables between can have negative effects on your application. When working with shared data, you can synchronize data access by primarily using:

  • Blocking methods such as locks and mutexes
  • Non-blocking methods such as lock-free programming techniques.

In this exercise you will learn how to use the critical_section blocking method to make sure that data access is synchronized between threads.

Part 1: Running the Unsynchronized Code

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

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

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

    C++

    parallel_invoke( [&] { FunctionA(); }, [&] { FunctionB(); } );
    Note:
    The parallel_invoke algorithm executes a set of tasks in parallel. It does not return until each task has completed. This algorithm is useful when you have several independent, unrelated tasks that you want to execute at the same time.

    The parallel_invoke algorithm takes as its parameters a series of lambda functions, function objects, or function pointers. The parallel_invoke algorithm is overloaded to take from two to ten parameters.

    In this case, the parallel_invoke call will create a couple of tasks that will execute FunctionA and FunctionB in parallel.

  4. Scroll up and examine the bodies of FunctionA and FunctionB

    Note:
    You’ll notice that both functions are incrementing the global variable number.

  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 times each function will increment the variable

    C++

    static const int NUM_ITERATIONS = 150000;

  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 most likely the final value of number is not 300,000. Both threads are sharing the same data (the numbers variable) and they are not synchronizing so that only one of them can read and write to the variable at a time. This is causing a data race between the threads, and can have adverse effects in any application with shared data.

  7. Run the application a couple more times and note that the final value of number is different from the previous run:

    Figure 2

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

Part 2: Synchronizing with critical_section

  1. Back in Visual Studio, scroll down to the main function.
  2. In order to synchronize data access between the threads, declare the following critical_section variable as the first line of the main function:

    C++

    critical_section mutex;
    Note:
    This critical_section type represents a non-reentrant, cooperative mutual exclusion object that uses concurrency runtime’s facilities to enable cooperative scheduling of work when blocked.

  3. Send the mutex as a parameter to FunctionA and FunctionB by making the following modifications to the parallel_invoke function:

    C++

    parallel_invoke( [&] { FunctionA(&mutex); }, [&] { FunctionB(&mutex); } );

  4. Scroll up until you see FunctionA.
  5. Change FunctionA’s declaration to specify that it requires a incoming parameter of type critical_section:

    C++

    void FunctionA(critical_section* pMutex)

  6. Inside FunctionA’s body, lock the mutex so that it will guarantee that this thread will be the only one changing the numbers variable by typing the following code above the line where number gets incremented:

    C++

    pMutex->lock();

  7. Once the variable is modified, we must release the mutex so that other threads that need to use it will be able to acquire it. Release the mutex by typing the following code above the line where number gets incremented:

    C++

    pMutex->unlock();

  8. Repeat steps 5-7 above but this time around with FunctionB.
  9. From the Debug menu, select Start Without Debugging (click Yes if prompted to build the project)
  10. You should now get the correct value from the number variable:

Figure 3

Note:
Run the program various times to make sure that the correct result is shown.The critical_section lock is now making sure that only one of the threads can modify the number variable; thus the program will not have any data races.