In computer science, a mutex (short for mutual exclusion) is a synchronization object that is used to protect shared resources, such as variables or data structures, from concurrent access by multiple threads or processes.

In simple words, a mutex acts like a gatekeeper that allows only one thread or process to access the shared resource at a time. When a thread or process wants to access the resource then it will try to acquire the mutex. If the mutex is already held by some other thread/process then thread or process that had requested the mutex will be blocked until the mutex becomes available.

So, we can say that acquiring a mutex is a block call and so all the processing of that thread/process will be stopped until it acquires that mutex. Once the thread or process acquires the mutex, it can safely access the shared resource. When it’s done then this thread/process will release the mutex so that other threads or processes can access the resource.

Mutex
Mutex

Example:

Suppose we have a bank account that can be accessed by multiple threads. If we don’t use a mutex to protect the account, two threads could try to withdraw money from the account at the same time, which could result in an inconsistent balance or other errors. To prevent this, we can use a mutex to ensure that only one thread can access the account at a time. Lets understand this in C code.

Mutex Example
Mutex

Let us say that there are many ways to withdraw money from your bank account e.g. Internet banking, mobile banking, through ATM, through bank branch etc. If somehow two or more services try to withdraw some amount from same account, then there will be a big issue. But we can use a mutex so that if one service is trying to withdraw money than other service will not be about to withdraw money at same time. The other service needs to wait. I hope the basic concept of mutex is clear to you now.

Advantages:

Mutual exclusion: Mutexes ensure that only one thread or process can access a shared resource at a time, which helps to prevent data corruption and inconsistencies that can arise from concurrent access.

Synchronization: Mutexes provide a way for threads or processes to coordinate their activities and synchronize access to shared resources. This can help to avoid race conditions and deadlocks.

Blocking: Mutexes can be used to block a thread or process until a shared resource becomes available. This can help to prevent busy-waiting and improve the overall efficiency of the system.

Disadvantages:

Overhead: Mutexes can introduce overhead in terms of CPU time and memory usage. Acquiring and releasing a mutex requires system calls and context switching, which can slow down the performance of the system.

Deadlocks: Mutexes can lead to deadlocks if they are not used correctly. A deadlock occurs when two or more threads or processes are waiting for each other to release a mutex, and none of them can proceed. (If you want to read more about deadlocks then kindly just go to deadlock location in this post).

Priority inversion: Mutexes can cause priority inversion, which occurs when a low-priority thread or process holds a mutex that a high-priority thread or process needs. This can lead to delays in the high-priority thread or process, which can impact the overall performance of the system.

Deadlock

Deadlock is a situation where two or more threads or processes are blocked, waiting for each other to release resources that they need to proceed, resulting in a stalemate. In the context of mutexes, deadlock can occur when multiple threads or processes attempt to acquire locks on the same set of resources in a different order.

#include <pthread.h>

pthread_mutex_t mutex1;
pthread_mutex_t mutex2;

void* thread1(void* arg) {
    while(1) {
        //do some work 
        pthread_mutex_lock(&mutex1);
        pthread_mutex_lock(&mutex2);
        // do critical some work
        pthread_mutex_unlock(&mutex2);
        pthread_mutex_unlock(&mutex1);
        //So some work
    }

    return NULL;
}

void* thread2(void* arg) {
    while(1) {
        pthread_mutex_lock(&mutex2);
        pthread_mutex_lock(&mutex1); 
        // do critical work
        pthread_mutex_unlock(&mutex1);
        pthread_mutex_unlock(&mutex2);
        //do some work
    }

    return NULL;
}

int main() {
    pthread_t t1, t2;

    pthread_mutex_init(&mutex1, NULL);
    pthread_mutex_init(&mutex2, NULL);

    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);

    return 0;
}

I

n this example, two threads are spawned, and each thread tries to acquire two mutexes, mutex1 and mutex2, in a different order. If thread 1 acquires mutex1 first and then tries to acquire mutex2, while thread 2 acquires mutex2 first and then tries to acquire mutex1, both threads will be blocked waiting for the other thread to release the mutex that it needs. This will result in a deadlock, and the program will be stuck.

To avoid deadlocks, it is important to ensure that all threads or processes acquire mutexes in the same order. This can be achieved by using a hierarchical locking protocol or by ensuring that all threads or processes access shared resources in a consistent order.

References

Related posts