Threads and race conditions

Please work out the pre-lab problems before your next lab section. The GSI/IAs will go over these problems during the lab section.

[pre-lab and in-lab] 1. Threads and races

Consider the following program, which uses C++ threads.

#include <iostream>
#include <thread>

const int NUM_THREADS = 2;
const int NUM_ITER = 20;

int counter = 0;

void child()
{
    for (int i=0; i<NUM_ITER; i++) {
        counter++;
    }
}

main()
{
    std::thread* t[NUM_THREADS];

    for (int i=0; i<NUM_THREADS; i++) {
        t[i] = new std::thread(child);
    }

    for (int i=0; i<NUM_THREADS; i++) {
        t[i]->join();
    }

    std::cout << "counter = " << counter << std::endl;
}

[pre-lab] A. Assume that assignment and addition (but not increment) are atomic. Determine the lower and upper bounds for the final value of the shared variable counter, and show thread interleavings that generate these lower and upper bounds.

[in-lab] B. Compile the program (g++ race.cc -pthread), then use a scripting language (e.g., bash, python, perl, etc.) to run it 10000 times and summarize the results. What are the smallest and largest value seen in your lab section? Do results vary by computer?

[in-lab] C. Change the program to use NUM_THREADS=5 and NUM_ITER=1000000, and run it a few times. What range of answers do you see? How often does it produce the answer that a naive programmer might expect?

[in-lab] D. Declare and use an std::mutex to protect the increment of counter, then re-run the program.

[pre-lab] 2. Implementing mutual exclusion with atomic load and atomic store

Consider the following pseudo-code, which attempts to provide mutual exclusion for a critical section using only atomic load and store operations.

/* Global variables */
bool active[2] = {false, false};
int turn = 0;

Thread 0

while (1) {
    active[0] = true;

    while (turn != 0){
	while (active[1]) {
	}
	turn = 0;
    }
    <critical section>
    active[0] = false;

    <do other stuff>
}

Thread 1

while (1) {
    active[1] = true;

    while (turn != 1) {
	while (active[0]) {
	}
	turn = 1;
    }

    <critical section>
    active[1] = false;

    <do other stuff>
}

Is this a correct solution? If yes, prove it. If not, provide a counter-example, i.e., an interleaving of threads that allow the two threads to simultaneously access their critical sections.