
Introduction
Thread synchronization is defined as a mechanism that assures that two or more concurrent processes or threads do not execute the same software segment known as a critical section at the same time.
Synchronization strategies are used to control the access of processes to critical sections.
When one thread begins executing the critical section (a serialized part of the program), the second thread must wait until the first thread has been completed.
If correct synchronization procedures are not used, it may result in a race issue in which variable values are unpredictable and fluctuate based on the timings of process or thread context switches.
Mutexes are the most widely used method for achieving thread synchronization.
Also see, Introduction to Process Synchronization, Multiprogramming vs Multitasking
Read about - Lock based protocol in DBMS
Mutex
- A Mutex is a lock that is set before utilizing a shared resource and is released after using it.
- No other thread may access the locked code after the lock is set.
- As a result, if thread t2 is scheduled while thread t1 is still using the shared resource and the code is locked by thread t1 using mutexes, thread t2 will be unable to access that piece of code.
- As a result, synchronized access to shared resources in the code is ensured.
Working of a Mutex
- Assume one thread has used mutex to lock a region of code and is executing it.
- If the scheduler decides to do a context switch, all other threads that are ready to execute the same region will be unblocked.
- Only one thread will make it to execution, but if it tries to execute the same piece of code that is already locked, it will go to sleep again.
- The context switch will happen repeatedly, but no thread will be able to execute the locked code until the mutex lock over it is released.
- Only the thread that locked the mutex will be able to unlock it.
- As a result, once a thread has locked a section of code, no other thread may execute that area until the thread that locked it has unlocked it.
As a result, while working on shared resources, this approach maintains thread synchronization.
The following two functions are used to create a lock once a mutex has been initialized:
- int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr): Creates a mutex, referenced by the mutex, with the attributes specified by attr. The default mutex property (NONRECURSIVE) is utilized if attr is NULL.
- If successful, it returns 0 and the mutex state becomes initialized and unblocked.
- If unsuccessful,it returns -1.
2. int pthread_mutex_lock(pthread_mutex_t *mutex) : Locks a mutex object, that identifies a mutex. If another thread has previously locked the mutex, the thread waits for it to become accessible. When a thread locks a mutex, it becomes the current owner and remains so until the same thread unlocks it. The lock may be used differently when a mutex contains the recursive attribute. When the same thread locks this type of mutex several times, the count is increased, and no waiting thread is posted. To decrease the count to zero, the owning thread must use pthread_mutex_unlock() the same number of times.
- If successful,returns 0.
- If unsuccessful,returns -1.
The mutex can be unlocked and destroyed by using the two functions listed below.
- int pthread_mutex_unlock(pthread_mutex_t *mutex) :Releases a mutex object. If one or more threads are waiting to lock the mutex, pthread_mutex_unlock() causes one of them to return from pthread_mutex_unlock() with the mutex object. The mutex unlocks with no current owner if no threads are waiting for it. When a mutex contains the recursive property, the lock may be used differently. When the same thread locks this type of mutex numerous times, unlock will decrement the count, and no waiting thread will be allowed to continue executing with the lock. If the count reaches zero, the mutex is released, and any thread is waiting for it is posted.
- If successful returns 0.
- If unsuccessful,returns -1.
2. int pthread_mutex_destroy(pthread_mutex_t *mutex) : Removes a mutex object, which is used to identify a mutex. Mutexes are used to secure shared resources. The mutex has been assigned to an incorrect value, but it may be re-initialized with the pthread_mutex_unlock() function.
- If successful returns 0.
- If unsuccessful,returns -1.
Example
This example shows how mutexes are used for thread synchronization
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
pthread_t thread_id[2];
int ctr;
pthread_mutex_t lock;
void *try_exmp(void *arg)
{
pthread_mutex_lock(&lock);
unsigned long i = 0;
ctr += 1;
printf("\n Task %d started\n", ctr);
for (i = 0; i < (0xFFFFFFFF); i++)
;
printf("\n Task %d finished\n", ctr);
pthread_mutex_unlock(&lock);
return NULL;
}
int main(void)
{
int i = 0;
int error;
if (pthread_mutex_init(&lock, NULL) != 0)
{
printf("\n mutex init has failed\n");
return 1;
}
while (i < 2)
{
error = pthread_create(&(thread_id[i]),NULL,
try_exmp,NULL);
if (error != 0)
printf("\nThread can't be created :[%s]",
strerror(error));
i++;
}
pthread_join(thread_id[0], NULL);
pthread_join(thread_id[1], NULL);
pthread_mutex_destroy(&lock);
return 0;
}
In the code above:
- At the start of the main function, a mutex is created.
- When utilizing the shared resource 'ctr,' the same mutex is locked in the 'try_exmp()' function.
- The identical mutex is unlocked at the end of the 'try_exmp()' function.
- The mutex is deleted at the end of the main function after both threads have completed their tasks.
Output
As a result, both start and finish logs of both the tasks are present this time.
As a result, Mutex was used to synchronize the threads.
Also Read About, FCFS Scheduling Algorithm