When there are multiple threads that share a common resource within the process boundary, the critical section of the code can be synchronized with a mutex lock. A mutex is a mutually exclusive lock that allows only one thread to access the critical block of code that is secured by a mutex. Let's take a simple example to understand the need for the mutex lock application practically.
Let's take a Bank Savings Account class that allows three simple operations, that is, getBalance, withdraw, and deposit. The Account class can be implemented as shown in the following code. For demonstration purposes, the Account class is designed in a simple fashion neglecting corner cases and validations that are required in the real world. It is simplified to the extent that the Account class doesn't even bother to capture the account number. I'm sure there are many such requirements that are quietly ignored for simplicity. No worries! Our focus is to learn mutex here with the shown example:
#include <iostream>
using namespace std;
class Account {
private:
double balance;
public:
Account( double );
double getBalance( );
void deposit ( double amount );
void withdraw ( double amount ) ;
};
The Account.cpp source file looks like this:
#include "Account.h"
Account::Account(double balance) {
this->balance = balance;
}
double Account::getBalance() {
return balance;
}
void Account::withdraw(double amount) {
if ( balance < amount ) {
cout << "Insufficient balance, withdraw denied." << endl;
return;
}
balance = balance - amount;
}
void Account::deposit(double amount) {
balance = balance + amount;
}
Now, let's create two threads, namely DEPOSITOR and WITHDRAWER. The DEPOSITOR thread is going to deposit INR 2000.00 while the WITHDRAWER thread is going to withdraw INR 1000.00 every alternate second. As per our design, the main.cpp source file can be implemented as follows:
#include <thread>
#include "Account.h"
using namespace std;
enum ThreadType {
DEPOSITOR,
WITHDRAWER
};
Account account(5000.00);
void threadProc ( ThreadType typeOfThread ) {
while ( 1 ) {
switch ( typeOfThread ) {
case DEPOSITOR: {
cout << "Account balance before the deposit is "
<< account.getBalance() << endl;
account.deposit( 2000.00 );
cout << "Account balance after deposit is "
<< account.getBalance() << endl;
this_thread::sleep_for( 1s );
}
break;
case WITHDRAWER: {
cout << "Account balance before withdrawing is "
<< account.getBalance() << endl;
account.deposit( 1000.00 );
cout << "Account balance after withdrawing is "
<< account.getBalance() << endl;
this_thread::sleep_for( 1s );
}
break;
}
}
}
int main( ) {
thread depositor ( threadProc, ThreadType::DEPOSITOR );
thread withdrawer ( threadProc, ThreadType::WITHDRAWER );
depositor.join();
withdrawer.join();
return 0;
}
If you observe the main function, the thread constructor takes two arguments. The first argument is the thread procedure that you would be familiar with by now. The second argument is an optional argument that can be supplied if you would like to pass some arguments to the thread function.