Parallel Paradigm: Exploring Mutex and Semaphore in OS

Nikhil Wani
5 min readNov 19, 2021

--

An Operating System can be defined as an interface between user and hardware. It is responsible for the execution of all the processes, Resource Allocation, CPU management, File Management and many other tasks.

Process Synchronization

Process Synchronization is a way to coordinate processes that use shared data. While executing many concurrent processes, process synchronization helps to maintain shared data consistency and cooperating process execution. Processes have to be scheduled to ensure that concurrent access to shared data does not create inconsistencies. Data inconsistency can result in what is called a race condition. A race condition occurs when two or more operations are executed at the same time, not scheduled in the proper sequence, and not exited in the critical section correctly.

1. Mutex

Mutual Exclusion object allows all the processes to use the same resource but at a time, only one process is allowed to use the resource. Mutex uses the lock-based technique to handle the critical section problem.

Whenever a process requests for a resource from the system, then the system will create a mutex object with a unique name or ID. So, whenever the process wants to use that resource, then the process occupies a lock on the object. After locking, the process uses the resource and finally releases the mutex object. After that, other processes can create the mutex object in the same manner and use it.

By locking the object, that particular resource is allocated to that particular process and no other process can take that resource. So, in the critical section, no other processes are allowed to use the shared resource. In this way, the process synchronization can be achieved with the help of a mutex object.

This is shown with the help of the following example

wait(mutex)
------
Critical Section
------
signal (mutex)

E.g.: Mutex

#include <stdio.h>
#include <pthread.h>

int count = 0;
pthread_mutex_t mutexCount;

void * incThread(void* data) {
while(1) {
pthread_mutex_lock(&mutexCount);
count++;
printf("Inc: %d\n", count);
pthread_mutex_unlock(&mutexCount);
}
}

void * decThread(void* data)
{
while(1) {
pthread_mutex_lock(&mutexCount);
count - ;
printf("Dec: %d\n", count);
pthread_mutex_unlock(&mutexCount);
}
}

int main(int argc, char const *argv[])
{
pthread_t incId, decId;
pthread_mutex_init(&mutexCount, NULL);
pthread_create(&incId, NULL, incThread, NULL);
pthread_create(&decId, NULL, decThread, NULL);
pthread_join(incId, NULL);
pthread_join(decId, NULL);
pthread_mutex_destroy(&mutexCount);
return 0;
}

E.g.: Mutex Deadlock

#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>

int count = 0;
pthread_mutex_t mutexCount = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
// pthread_mutex_t mutexCount = PTHREAD_MUTEX_INITIALIZER;

void * incThread(void* data) {
while(1) {
pthread_mutex_lock(&mutexCount);
count++;
printf("Inc: %d\n", count);
pthread_mutex_unlock(&mutexCount);
}
}

void * decThread(void* data) {
while(1) {
pthread_mutex_lock(&mutexCount);
printf("Locked once\n");
pthread_mutex_lock(&mutexCount);
printf("Locked second time\n");
count--;
printf("Dec: %d\n", count);
pthread_mutex_unlock(&mutexCount);
pthread_mutex_unlock(&mutexCount);
}
}

int main(int argc, char const *argv[])
{
pthread_t incId, decId;
pthread_create(&incId, NULL, incThread, NULL);
pthread_create(&decId, NULL, decThread, NULL);
pthread_join(incId, NULL);
pthread_join(decId, NULL);
pthread_mutex_destroy(&mutexCount);
return 0;
}

2. Semaphore

Semaphore is an integer variable S, that is initialized with the number of resources present in the system and is used for process synchronization. It uses two functions to change the value of S i.e. wait() and signal().

Wait: The wait operation decrements the value of its argument S if it is positive. If S is negative or zero, then no operation is performed.

wait(S)
{
while(S<=0)
S--
}

2. Signal for the process synchronization: The signal operation increments the value of its argument S.

Signal(S)
{
S++
}

There are two categories of semaphores i.e. Counting semaphores and Binary semaphores.

In Counting semaphores, firstly, the semaphore variable is initialized with the number of resources available. After that, whenever a process needs some resource, then the wait() function is called and the value of the semaphore variable is decreased by one. The process then uses the resource and after using the resource, the signal() function is called and the value of the semaphore variable is increased by one. So, when the value of the semaphore variable goes to 0 i.e. all the resources are taken by the process and there is no resource left to be used, then if some other process wants to use resources then that process has to wait for its turn. In this way, we achieve the process synchronization.

In Binary semaphores, the value of the semaphore variable will be 0 or 1. Initially, the value of semaphore variable is set to 1 and if some process wants to use some resource then the wait() function is called and the value of the semaphore is changed to 0 from 1. The process then uses the resource and when it releases the resource then the signal() function is called and the value of the semaphore variable is increased to 1. If at a particular instant of time, the value of the semaphore variable is 0 and some other process wants to use the same resource then it has to wait for the release of the resource by the previous process. In this way, process synchronization can be achieved. It is similar to mutex but here locking is not performed.

E.g.: Semaphore

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

int count = 0;
sem_t semCount;

void * incThread(void* data) {
while(1) {
sem_wait(&semCount);
count++;
printf("Inc: %d\n", count);
sem_post(&semCount);
}
}

void * decThread(void* data) {
while(1) {
sem_wait(&semCount);
count--;
printf("Dec: %d\n", count);
sem_post(&semCount);
}
}

int main(int argc, char const *argv[])
{
pthread_t incId, decId;
sem_init(&semCount, 0, 1);
pthread_create(&incId, NULL, incThread, NULL);
pthread_create(&decId, NULL, decThread, NULL);
pthread_join(incId, NULL);
pthread_join(decId, NULL);
sem_destroy(&semCount);
return 0;
}

Difference between Mutex and Semaphore

  • Mutex uses a locking mechanism i.e. if a process wants to use a resource then it locks the resource, uses it and then release it. But on the other hand, semaphore uses a signaling mechanism where wait() and signal() methods are used to show if a process is releasing a resource or taking a resource.
  • A mutex is an object but semaphore is an integer variable.
  • In semaphore, we have wait() and signal() functions. But in mutex, there is no such function.
  • A mutex object allows multiple process threads to access a single shared resource but only one at a time. On the other hand, semaphore allows multiple process threads to access the finite instance of the resource until available.
  • In mutex, the lock can be acquired and released by the same process at a time. But the value of the semaphore variable can be modified by any process that needs some resource but only one process can change the value at a time.

More Details about OS concepts in C Check the link below :

Thank You!!!!

--

--