Threads Race Condition
In order to understand Race condition lets take an example of multithreaded application . An application storing studentId of 5 students in queue. Job of the application is to print the studentID . For printing studentID we have created 2 threads printIdThread1 and printIdThread2. . Ideally we should get studentId from 1to 5 as output of below application. It is interesting to watch output of below application. This application has no mechanism for thread synchronization with mutex.
#include <iostream> // main header
#include <thread> // for thread
#include <queue> // for queue
using namespace std;
std::queue<int> studentId;
void printId(int threadId)
{
if( studentId.empty())
{
cout<<" No action required job already done";
return;
}
while(!studentId.empty())
{
int a = studentId.front();
std::cout << "Thread Id "
<< threadId
<<" Value "
<< a << endl;
studentId.pop();
}
}
int main ()
{
for(int i =1;i<=5;i++)
studentId.push(i);
// inserting studentId value into Queue
std::thread printIdThread1 (printId,1); // 1st Thread
std::thread printIdThread2 (printId,2); // 2nd Thread
printIdThread1 .join(); //Main thread waiting for thread1
printIdThread2 .join(); //Main thread waiting for thread1
cout << "Main job done"<< endl;
return 0;
}
Both printIdThread1 and printIdThread2 tried to access global studentId at same time. Threads even tried to modified global studentId by popping out studentId from queue. For above application output is not fixed. It depends on race between the threads.
In Summary, Race condition occurred between 2 or more threads when they tried to access and modify the same resource at same time. As a results application may not perform correctly . Some time results are so much catastrophic that application may crash also .

Various techniques of Threads Synchronization with Mutex
In above application we have seen issue with C++ multiple threads .In order to solve above problem we have various thread synchronization mechanism. From name it is clear we are trying to synchronizing threads so that output of application can be determined. No surprises and application will work as per user expectation.
In today’s article we will be mainly discussing about mutex and its types.
Mutex
Mutex is one of the best and easy technique for thread synchronization. Threads need to lock and unlock mutex while accessing the shared resources . For example, if we have 2 threads t1 and t2 and they want to access shared resource s1. Both t1 and t2 need to call mutex lock. Suppose, if t1 call mutex lock first so t2 need to wait on mutex lock until t1 call mutex unlock.
With synchronization, threads will not Race and corrupt shared resources. They will wait for their turn and ideally will move in sequential manner. So no corruption of global or shared data.
See below updated application with Mutex.
#include <iostream> // main header
#include <thread> // for thread
#include <queue> // for queue
#include<mutex> // for mutex
using namespace std;
std::queue<int> studentId;
std::mutex m1;
void printId(int threadId)
{
m1.lock(); //Critical Section Starts
while(!studentId.empty())
{
int a = studentId.front();
std::cout << "Thread Id "
<< threadId
<<" Value "
<<a
<<endl;
studentId.pop();
}
m1.unlock(); //Critical Section Ends
}
int main ()
{
for(int i =1;i<=5;i++)
studentId.push(i); // inserting value into Queue
std::thread printIdThread1 (printId,1); // 1st Thread
std::thread printIdThread2 (printId,2); // 2nd Thread
printIdThread1.join(); //Main thread waiting for thread1
printIdThread2.join(); //Main thread waiting for thread1
cout<< "Main job done" << endl;
return 0;
}
In above example, one thread is locking the mutex and printing all the values.
Meanwhile other thread will also be blocked for its turn on mutex.
Other thread is only printing ” No action needed” as studentId is already printed.

lock_guard
As we discussed mutex is definitely good option for doing thread synchronization and protect shared data. With mutex there is one drawback that it is must for thread to unlock the mutex .Otherwise ,other threads waiting for mutex will never be able to move further .They will get blocked for always.
Lets see an example below where function returns on empty queue and forgots to release mutex.
#include <iostream> // main header
#include <thread> // for thread
#include <queue> // for queue
#include<mutex> // for mutex
using namespace std; //for namespace
std::queue<int> s1;
std::mutex m1;
void popQueue(int threadId)
{
m1.lock();
if( s1.empty())
{
cout << "No action required job already done ThreadId "
<< threadId << endl;
return; // forget to unlock mutex
}
while(!s1.empty())
{
int a = s1.front();
std::cout << "Thread Id "
<< threadId
<<" Value "
<<a
<<endl;
s1.pop();
}
m1.unlock();
}
int main ()
{
for(int i =1;i<=5;i++)
s1.push(i); // instering value into Queue
std::thread popThread1(popQueue,1); // 1st Thread
std::thread popThread2(popQueue,2); // 2nd Thread
std::thread popThread3(popQueue,3); // 3rd Thread
cout<<"waiting for thread exit"<< endl;
popThread1.join(); //Main thread waiting for thread1
cout<<"Thread 1 exit"<<endl;
popThread2.join(); //Main thread waiting for thread2
cout<<"Thread 2 exit"<<endl;
popThread3.join();
cout<<"Thread 3 exit"<<endl;
cout<< "Main job done"<<endl;
return 0;
}
In above example if queue is empty ,thread will return after locking mutex . Other threads which are waiting for mutex lock will get blocked for ever.

Solution for above problem is lock_guard.
Lock_guard will take care of mutex lock and unlock .
In lock_guard mutex is added as argument during construction. Therefore, if some exception occurred or user forgot to unlock mutex, local variable lock_guard will get destroyed automatically. On Destruction lock_gurad in turn releases mutex.
#include <iostream> // main header
#include <thread> // for thread
#include <queue> // for queue
#include<mutex> // for mutex
using namespace std; //for namespace
std::queue<int> s1;
std::mutex m1;
void popQueue(int threadId)
{
//m1.lock();
std::lock_guard<std::mutex>lck(m1);
if( s1.empty())
{
cout<<"No action required job already done ThreadId "
<< threadId << endl;
return;
}
while(!s1.empty())
{
int a = s1.front();
std::cout << "Thread Id "
<< threadId
<< " Value "
<< a
<< endl;
s1.pop();
}
}
int main ()
{
for(int i =1;i<=5;i++)
s1.push(i); // inserting value into Queue
std::thread popThread1(popQueue,1); // 1st Thread
std::thread popThread2(popQueue,2); // 2nd Thread
std::thread popThread3(popQueue,3); // 2nd Thread
cout<<"waiting for thread exit"<<endl;
popThread1.join(); //Main thread waiting for thread1
cout<<"Thread 1 exit"<<endl;
popThread2.join(); //Main thread waiting for thread2
cout<<"Thread 2 exit"<<endl;
popThread3.join();
cout<<"Thread 3 exit"<<endl;
cout<< "Main job done"<<endl;
return 0;
}
With lock_guard mutex now user do not have to bother about mutex lock unlock .Lock_gurad variable will take care.

Recursive_Mutex for thread synchronization
Recursive_Mutex as it is clear from its name it is recursive in nature. Means, same thread can lock same mutex multiple times .
Lets take an example below where we are inserting and printing data .
#include <iostream> // main header
#include <thread> // for thread
#include <queue> // for queue
#include<mutex> // for mutex
using namespace std;//for namespace
std::queue<int> s1;
std::mutex m1;
void popQueue()
{
std::lock_guard<std::mutex>lck(m1); //Second Lock
if( s1.empty())
{
cout << "No action required job already done ThreadId "
<< endl;
return;
}
cout<<"Data started to popout"<<endl;
while(!s1.empty())
{
int a = s1.front();
std::cout << " Value "<<a<<endl;
s1.pop();
}
}
void pushData() //Start here
{
std::lock_guard<std::mutex>lck(m1); //First Lock
for(int i =1;i<=5;i++)
s1.push(i); // inserting value into Queue
cout<<"Data inserted"<<endl;
popQueue();
}
int main ()
{
std::thread popThread1(pushData); // 1st Thread start
cout<<"waiting for thread exit"<<endl;
popThread1.join(); //Main thread waiting for thread1
cout<< "Main job done"<<endl;
return 0;
}
As, we have seen above after getting pop out no data is getting printed since same thread locked the mutex 2 times .Due to which it get blocked and finally full application is blocked.

So solution for above problem is just to replace std::mutex with std::recuresive_mutex .
#include <iostream> // main header
#include <thread> // for thread
#include <queue> // for queue
#include<mutex> // for mutex
using namespace std;//for namespace
std::queue<int> s1;
std::recursive_mutex m1;
void popQueue()
{
std::lock_guard<std::recursive_mutex>lck(m1);
//Second Lock
if( s1.empty())
{
cout<<"No action required job already done ThreadId "
<< endl;
return;
}
cout<<"Data started to popout"<<endl;
while(!s1.empty())
{
int a = s1.front();
std::cout << " Value "<<a<<endl;
s1.pop();
}
}
void pushData()
{
std::lock_guard<std::recursive_mutex>lck(m1);
//First Lock
for(int i =1;i<=5;i++)
s1.push(i); // inserting value into Queue
cout<<"Data inserted"<<endl;
popQueue();
}
int main ()
{
std::thread popThread1(pushData); // 1st Thread start
cout<<"waiting for thread exit"<<endl;
popThread1.join(); //Main thread waiting for thread1
cout<< "Main job done"<<endl;
return 0;
}
Output

Timed and Recursive_timed Mutex
Timed and recursive_timed mutex is mainly used for not waiting indefinitely for mutex.
As we have seen in above cases if mutex is not available thread will try to block indefinitely. But with timed mutex thead will only wait for mutex for specified time. Once that time elapses thread will comeout from blcoked state with proper return value try_lock_for () .
#include <iostream> // main header
#include <thread> // for thread
#include <queue> // for queue
#include<mutex> // for mutex
using namespace std;//for namespace
std::queue<int> s1;
std::timed_mutex m1;
void popQueue2()
{
int d1 =m1.try_lock_for(std::chrono::seconds(5));
if(d1 ==0)
{
cout<<" Some issue in the code "<<endl;
return;
}
cout<<"Data started to popout"<<endl;
while(!s1.empty())
{
int a = s1.front();
std::cout << " Value "<<a<<endl;
s1.pop();
}
}
void popQueue1()
{
//std::lock_guard<std::timed_mutex>lck1(m1);
m1.try_lock_for(std::chrono::seconds(5));
while(!s1.empty())
{
int a = s1.front();
std::cout << " Value "<<a<<endl;
s1.pop();
}
std::this_thread::sleep_for(std::chrono::seconds(10));
m1.unlock();
}
int main ()
{
for(int i =1;i<=5;i++)
s1.push(i); // inserting value
std::thread popThread1(popQueue1); // 1st Thread
std::thread popThread2(popQueue2); // 1st Thread
cout<<"waiting for thread exit"<<endl;
popThread1.join(); //Main thread waiting for thread1
popThread2.join();
cout<< "Main job done"<<endl;
return 0;
}
Output

Main Funda: There are multiple types of mutex and those are very important to avoid race condition between threads.