C++ Mutex and Preventing Race conditions

In this article I will talk about race conditions and how to use a mutex in C++ to protect yourself from these race conditions.
Firstly we need to understand what a race condition is. In its simplest form it is where a piece of data changes and your thread did not change the data or you were not expecting the data to change. Lets look at a simple example



class myIntClass {
public:
int iVal{0};

public:
//cons
myIntClass(int iInitial){
iVal = iInitial;
}

int get(){
return iVal;
}

int incrementAndGet(){
iVal++;
return iVal;
}
};




Now imagine you have two threads calling incrementAndGet and you are using this variable as a counter of unique hits on a website. The issue is that if two threads simultaneously call incrementAndGet the results would interleave. The reason being that iVal++ is not a single operation but a compound operation. Firstly you get the value then you add one to the value. If one thread gets the value while the other thread does the ++ then you will have inconsistent results.

This is an important caveat you must think about when writing threads and that is
"all compound actions must be done atomically" ie at the same time and while you do the get and ++ you need to stop others from touching that variable while you are exclusively working on it. This means the variable is mutually exclusive to you. In order to aid us there is a class std::mutex that we can use to create a guard that will help us protect our variable. Here we are really trying to create iVal and use it as an atomic int. In order to make this more atomic look at this class



/*
Class for demo purposes only in the real world use
struct Counters { int a; int b; }; // user-defined trivially-copyable type
std::atomicCounters> cnt; // specialization for the user-defined type
*/
class AtomicInt {
private:
int iVal{0};
std::mutex iValMutex;

public:
//cons
AtomicInt(int iInitial){
iVal = iInitial;
}

int get(){
std::lock_guard<std::mutex> guard(iValMutex);
return iVal;
//Lock out of scope so lock ceases
}

int incrementAndGet(){
std::lock_guard<std::mutex> guard(iValMutex);
//compound action of = and ++ so must be guarded
iVal++;
return iVal;
//Lock out of scope so lock ceases
}
};

int mutex_main(){
AtomicInt *myAtomicInt = new AtomicInt(0);
std::cout << myAtomicInt->get() <<std::endl;
std::cout << myAtomicInt->incrementAndGet() <<std::endl;;
//etc……




Now we can see that the iVal has a guard that allows it to be protected and as long as you use this guard that variable will be mutually exclusive to you. All you need is to create a mutex and then pass it to a lock_guard, once the lock_guard is out of scope the lock ceases. Note a guard_lock is always attached to a mutex lock. When you create the guard it implicitly locks the mutex. When the guard_lock goes out of scope it invokes its destructor which explicitly unlocks the lock of the mutex. This automatic unlocking of the lock in the Destructor is part of a pattern called RAII or Resource Acquisition is Initialization. You are essentially saying I will acquire the mutex resource and I will manage the lifecycle including calling unlock when it goes out of scope.

A mutex should be thought about as a Critical Section or Mutual Exclusion section hence the name.

You can guard far more than just variables, you can guard anything. In the following example we will guard a std::list. Providing this list is only accessible by you and you always use the mutex you can prevent changes - Usually you would wrap the list as private in a class and only allow access to the list from the class. You would then only need to worry about leaking a reference to the list. In order to prevent this leak when returning a copy of the list to a caller we return a shallow copy. In the example we make a shallow copy so the caller can't change the list behind my back and if they do its their own copy so my list still remains intact. Lets look at the code



std::list<int> myList;
std::mutex myListMutex;

void addToList(int newValue)
{
std::lock_guard<std::mutex> guard(myListMutex);
myList.push_back(newValue);
//Guard lock now out of scope
}

bool listContains(int value_to_find)
{
std::lock_guard<std::mutex> guard(myListMutex);
return std::find(myList.begin(),myList.end(),value_to_find) != myList.end();
//Guard lock now out of scope
}

std::list<int> getList(){
std::lock_guard<std::mutex> guard(myListMutex);
//never return list itself - The user could modify it and affect you
//return a copy of the list so you remain thread safe
//Shallow copy as the type is int if it was a ref Object you would need a deep copy
std::list<int> returnList(myList.begin(), myList.end());
return returnList;
//Guard lock now out of scope
}




Just for completeness I include the entire file here so you can play with it, All code should work on C++11 and C++17



//
// Mutex.cpp
// c++Test
//
// Created by ARIF JAFFER
// Copyright © 2020 ARIF JAFFER. All rights reserved.
//

#include "Mutex.hpp"

#include list>
#include mutex>
#include algorithm>
#include iostream>

/*
Sample functions on how to lock a list and Guard it
Indentation is explicit to show the lock scope

*/
std::list<int> myList;
std::mutex myListMutex;

void addToList(int newValue)
{
std::lock_guard<std::mutex> guard(myListMutex);
myList.push_back(newValue);
//Guard lock now out of scope
}

bool listContains(int value_to_find)
{
std::lock_guard<std::mutex> guard(myListMutex);
return std::find(myList.begin(),myList.end(),value_to_find) != myList.end();
//Guard lock now out of scope
}

std::list<int> getList(){
std::lock_guard<std::mutex> guard(myListMutex);
//never return list itself - The user could modify it and affect you
//return a copy of the list so you remain thread safe
//Shallow copy as the type is int if it was a ref Object you would need a deep copy
std::list<int> returnList(myList.begin(), myList.end());
return returnList;
//Guard lock now out of scope
}


/*
Class for demo purposes only in the real world use
struct Counters { int a; int b; }; // user-defined trivially-copyable type
std::atomic cnt; // specialization for the user-defined type
*/
class AtomicInt {
private:
int iVal{0};
std::mutex iValMutex;

public:
//cons
AtomicInt(int iInitial){
iVal = iInitial;
}

int get(){
std::lock_guard<std::mutex> guard(iValMutex);
return iVal;
//Lock out of scope so lock ceases
}

int incrementAndGet(){
std::lock_guard<std::mutex> guard(iValMutex);
//compoud action of = and ++ so must be guarded
iVal++;
return iVal;
//Lock out of scope so lock ceases
}
};

int mutex_main(){
AtomicInt *myAtomicInt = new AtomicInt(0);
std::cout << myAtomicInt->get() <<std::endl;
std::cout << myAtomicInt->incrementAndGet() <<std::endl;;

std::cout << "Now Test List " << std::endl;
const int size=10;
for(int i=0; i addToList(i);

std::list<int> thisList=getList();
for(auto elem: thisList){
std::cout << elem << std::endl;

}

if(listContains(size))
std::cout << size << " in List"<< std::endl;
else
std::cout << size << " not in List"<< std::endl;

//list is 10 elems 0 to 9 inclusive
//formally list is size elems 0 to size-1 inclusive
assert(!listContains(size));

return 0;
}




You may be wondering how you acquire more than one lock at a time that you may need for processing. For this you can use scoped_lock which captures more than 1 lock. However if you take both locks you are blocking everyone else from using those locks, You must when you finish ensure scoped_lock goes out of scope to prevent deadlock.

Deadlock is the process where you are waiting on a lock to proceed and another thread is waiting on a lock that you currently hold in order to proceed. This means you are waiting for the other thread and the other thread is waiting for you. This is a stalemate or deadlock. One sure fire way to prevent this is to lock all resources you need together as an all or nothing or to ensure that all threads in the system take locks in the same order. This way you will not get to the situation where you are waiting on something that someone else holds while they wait for a lock you hold. The ordering means only one of you is in that situation and will free up when the lock is available.

While on the subject of locks I should mention that mutex locking mechanisms are not reentrant. This means that if you have the lock and you re-enter the same piece fo code and try to acquire the lock again you will block, If you think you will need to go through the code that creates the lock more than once you will likely have to use a reentrant_lock.

All of these other situations are other articles that I will post as time permits.


People who enjoyed this article also enjoyed the following:


Creating C++ threads
Low Latency Java using CAS and LongAdder
Naive Bayes classification AI algorithm
K-Means Clustering AI algorithm
Equity Derivatives tutorial
Fixed Income tutorial


And the following Trails:

C++
Java
python
Scala
Investment Banking tutorials

HOME
homeicon




By clicking Dismiss you accept that you may get a cookie that is used to improve your user experience and for analytics.
All data is anonymised. Our privacy page is here =>
Privacy Policy
This message is required under GDPR (General Data Protection Rules ) and the ICO (Information Commissioners Office).