DEV Community

clusterO
clusterO

Posted on

Threading

The best way to understand the problem is to take a look at the following example. We have two threads that both write to the same file.

// Thread 1 
file.write("Thread 1"); 
// Thread 2 
file.write("Thread 2");
Enter fullscreen mode Exit fullscreen mode

Now, suppose that Thread 1 finishes first, but then Thread 2 is preempted by another thread and has to wait for a while before it can continue execution. If this happens, the operating system has to suspend Thread 2 so that it doesn't try to write to the file while Thread 1 is still writing to it. But what if Thread 1 is still writing to the file when Thread 2 gets preempted? In this case, Thread 2 will be suspended and then resumed again, at which point it will try to write to the file again, which will overwrite whatever was written by Thread 1!

This is called a race condition and it's an easy mistake to make in multi threaded programming. The solution is to make sure that only one thread writes to the file at a time by using a lock:

// Thread 1 
lock(file) { 
file.write("Thread 1"); 
} 
// Thread 2 
lock(file) { 
file.write("Thread 2"); 
}
Enter fullscreen mode Exit fullscreen mode

This way, we ensure that only one thread can be writing to the file at any given time. This works fine as long as we only have two threads writing to the same file, but what if we have more than two threads? In this case, we need some way of ensuring that each thread gets its turn with the lock. This is where condition variables come in. A condition variable allows one thread to wait for another thread to release a lock, and vice versa. Here's how we would use them:

// Thread 1 
lock(file) { 
while (!fileIsWritable) 
wait(lock); 
file.write("Thread 1"); 
} 
// Thread 2 
lock(file) { 
while (!fileIsWritable) 
wait(lock); 
file.write("Thread 2"); 
} 
// Wait until Thread 1 has finished waiting 
bool done = false; 
while (!done) { 
if (signalAndWait(lock)) 
done = true; 
}
Enter fullscreen mode Exit fullscreen mode

The signalAndWait function takes a condition variable and a mutex and releases the mutex so that another thread can acquire it while simultaneously signaling the condition variable so that any threads waiting on it can wake up and continue execution. The signalAndWait function returns true if it was able to release the mutex, false otherwise (for example, if no other threads were waiting on the mutex). The code above shows how we would use signalAndWait in practice: we call signalAndWait on a mutex locked by another thread and then loop until it returns true . The signalAndWait function will release the mutex and then wait on the condition variable until another thread calls signalAndWait or until a timeout occurs (in which case it returns false ).

This ensures that every thread gets its turn with the mutex. If all threads are able to acquire the mutex without having to wait for any other threads, then there are no race conditions and everything works fine. However, if one of the threads has to wait for another thread before acquiring the mutex, then there's a race condition since another thread might acquire the mutex before our waiting thread does and overwrite our data! Note that signalAndWait is not available in Visual Studio 2005 or earlier versions of Visual C++, but you can get it from Boost.

We've seen how we can use locks and condition variables together to ensure that there are no race conditions when multiple threads access shared data. However, locks and condition variables aren't always easy or convenient to use in practice. Locks take up a lot of space in your code because they require you to put code inside critical sections (code blocks surrounded by locks), so you often have many locks scattered throughout your code. It's also easy to accidentally forget to lock a critical section, which can lead to subtle bugs.

Top comments (0)