A mutex is a type of lock used whenever multiple programs are using a shared resource to ensure that there isn't an invalid update of the shared resource.
Consider that we have two programs, namely A and B. Additionally, we have an integer variable x shared between both programs with two instructions that update the shared variable. In such a case, a mutex can ensure synchronization. In a CPU scheduling algorithm that simultaneously interrupts and runs processes that may result in incorrect values, we can ensure that both processes update the variable correctly.
Lock: A program locks the mutex so that no other program can use the shared resources until the unlock function is called.
Unlock: Once a program finishes dealing with the shared resources, it will call the unlock function to release the lock so the other process can use it.
Let's say we have two processes A and B, and a variable i set to 0. Both processes need to call the operation i = i + 1, where i is a shared resource among both processes. Now, there is a possibility that we have an incorrect update of the value of i. Instead of i = 2, we may get i = 1. The diagram below explains this phenomenon.
We will solve the issue using a mutex. We will call the section which updates the variable i in both processes the critical section. A critical section is a piece of code that is shared among multiple processes. We will then implement a mutex around the critical section to ensure proper synchronization.
Every time a process tries to enter the critical section to execute the code written, it will check the status of the lock. If a process holds the lock, it means it is currently in the critical section. Hence, other processes can not enter and will have to wait for the initial process which holds the lock to finish its execution of the critical section and call unlock. Only then will another thread enter the critical section.
Below you can see a diagram that explains how a mutex is used which ensures proper synchronization of the shared variable i.
It ensures mutual exclusion among multiple processes so that no two processes can access a critical section at the same time.
It safely executes multiple threads so that there is no data inconsistency between the threads in a multithreaded program.
It causes a process to be blocked/sleep if it tries to access a critical section for which the lock is already held by another process.
It ensures proper synchronization between threads to avoid conflicts.
Non-recursive mutex: A process can only lock and unlock this mutex once.
Recursive mutex: A process can call the lock function and lock this mutex however many times it wants. It must also call the unlock the same number of times to free this mutex.
Timed mutex: Allows a process to try to acquire a lock for a given period of time.
Priority inheritance mutex: Ensures that the process with high priority gets hold of a mutex before the ones with low priority.
Read-write mutex: Since there can be multiple reader and writer processes that wait on the mutex, this type of mutex ensures that only one writer program can hold the mutex at a time. Similarly, multiple reader processes can hold the mutex at once if there are no writer programs doing the same. This can be used in the infamous Producer-Consumer Problem.
To further understand how a mutex works, we will look at the C programs below, which show the working of the mutex from a coding point of view.
We see that a program with two threads, where each thread shares a variable counter and tries to update the variable without using a mutex, will yield incorrect results.
#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>// declare the variable counter as a global variableint counter;// our thread function which will be ran by the threadsvoid* thread_function(void* arg){// increment the value of the shared variable (counter) 1000000 for each threadfor(int i = 0; i < 1000000; i++){counter = counter + 1;}return NULL;}int main(void){pthread_t tid[2];// create two threads to run the thread function which has the shared resourcepthread_create(&tid[0], NULL, &thread_function, NULL);pthread_create(&tid[1], NULL, &thread_function, NULL);// wait on both threads to finish executionpthread_join(tid[0], NULL);pthread_join(tid[1], NULL);// Value Should Be 2000000printf("Value Of Counter: %d\n", counter);return 0;}
#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>// declare the variable counter as a global variableint counter;// initialize the mutexpthread_mutex_t mutex;// our thread function which will be ran by the threadsvoid* thread_function(void* arg){// increment the value of the shared variable (counter) 100000 for each threadfor(int i = 0; i < 1000000; i++){// lock the mutexpthread_mutex_lock(&mutex);// increment the shared variablecounter = counter + 1;// unlock/release the mutexpthread_mutex_unlock(&mutex);}return NULL;}int main(void){pthread_t tid[2];// create two threads to run the thread function which has the shared resourcepthread_create(&tid[0], NULL, &thread_function, NULL);pthread_create(&tid[1], NULL, &thread_function, NULL);// wait on both threads to finish executionpthread_join(tid[0], NULL);pthread_join(tid[1], NULL);// Value Should Be 2000000printf("Value Of Counter: %d\n", counter);return 0;}
To get the correct result in the program we saw earlier, we will include a mutex, lock it before we update the shared variable, and unlock it after the update. The result will show that the threads are properly synchronized with the help of a mutex.
Free Resources