5-Process Synchronisation
5-Process Synchronisation
A solution to the critical section problem must satisfy the following three conditions:
int counter = 0;
void P1() {
counter = counter + 1;
}
void P2() {
counter = counter - 1;
}
If both processes execute simultaneously without synchronisation, the final value of the
counter can be unpredictable, leading to inconsistencies.
1. flag[i]: An array where flag[i] = true indicates that process i wants to enter the
critical section.
2. turn: A variable indicating whose turn it is to enter the critical section.
Critical Section
Peterson’s Algorithm in C
#include <stdio.h>
#include <pthread.h>
#include <stdbool.h> #define NUM_ITERATIONS 5 volatile bool flag[2] =
{false, false}; volatile int turn; void *process(void *arg) {
int i = *(int *)arg;
int j = 1 - i; // The other process for (int k = 0; k <
NUM_ITERATIONS; k++) {
// Entry Section
flag[i] = true;
turn = j;
while (flag[j] && turn == j);
} // Critical Section
printf("Process %d is in the critical section.\n", i); // Exit Section
flag[i] = false;
}
return NULL;
} int main() {
pthread_t t0, t1;
int p0 = 0, p1 = 1; pthread_create(&t0, NULL, process, &p0);
pthread_create(&t1, NULL, process, &p1); pthread_join(t0, NULL);
pthread_join(t1, NULL); return 0;
}
Limitations
Works only for two processes (not scalable for multiple processes).
Requires busy waiting, which can be inefficient.
1. Locked (Acquired)—A thread that locks the mutex gains exclusive access to the
critical section.
2. Unlocked (Released)—When the thread releases the mutex, other threads can acquire
it.
#include <stdio.h>
#include <pthread.h>
int counter = 0; // Shared resource
void *increment(void *arg)
{
for (int i = 0; i< 1000000; i++)
{
counter++; // Race condition (no protection)
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, increment, NULL);
pthread_create(&t2, NULL, increment, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL); printf("Final Counter Value: %d\n", counter); //
Inconsistent result due to race condition
return 0;
}
Issue: If both threads modify the counter simultaneously, operations can interleave
incorrectly, leading to inconsistent results.
#include <stdio.h>
#include <pthread.h> int counter = 0;
pthread_mutex_tlock; // Mutex declaration void *increment(void *arg) {
for (int i = 0; i< 1000000; i++) {
pthread_mutex_lock(&lock); // Acquire lock
counter++;
pthread_mutex_unlock(&lock); // Release lock
}
return NULL;
} int main() {
pthread_t t1, t2;
pthread_mutex_init(&lock, NULL); // Initialise mutex pthread_create(&t1,
NULL, increment, NULL);
pthread_create(&t2, NULL, increment, NULL); pthread_join(t1, NULL);
pthread_join(t2, NULL); pthread_mutex_destroy(&lock); // Destroy mutex
printf("Final Counter Value: %d\n", counter); // Correct value (no race
condition)
return 0;
}
Solution: The mutex ensures that only one thread modifies the counter at a time,
preventing race conditions.
Mutual Exclusion: Only one thread can hold the lock at a time.
Blocking: If a thread tries to acquire a locked mutex, it must wait.
Explicit Locking & Unlocking: The programmer must manually acquire and release
the lock.
Drawbacks of Mutexes
Monitor Structure
A monitor consists of
How It Works:
If a process wants to use a resource, it calls acquire(). If another process is using it,
it waits.
When the process is done, it calls release(), which signals waiting processes.
class MonitorExample {
private int resource = 0; public synchronised void acquire() {
while (resource == 1) {
try { wait(); } catch (InterruptedException e) {}
}
resource = 1;
} public synchronised void release() {
resource = 0;
notify(); // Wake up a waiting thread
}
}
Benefit: The synchronised keyword ensures that only one thread enters the method at a
time.
Advantages of Monitors
Disadvantages of Monitors
Consider a text editor and an auto-save feature running as separate processes. The auto-save
process needs access to the same file as the text editor. Without synchronisation, data
inconsistencies or corruption may occur.
4. Race Condition
A race condition occurs when multiple processes or threads access and modify shared data
concurrently, leading to unpredictable and inconsistent outcomes depending on the order of
execution.
4.1 Example of a Race Condition In a banking application, multiple users may try to
transfer money from the same account at the same time. If proper synchronisation
mechanisms are not in place, it could result in incorrect balances or even lost transactions.
This can lead to financial loss and customer dissatisfaction.
If two users simultaneously attempt to withdraw $500, both processes may read the balance
as $1000 before updating it, leading to an incorrect final balance of $500 instead of $0.
For instance, when a customer is withdrawing money from an ATM, a semaphore can
be used to ensure that only one transaction is processed at a time to avoid
overdrawing the account. This ensures that the balance is correctly updated and
prevents any errors in the transaction process.
Binary Semaphore: Takes values 0 and 1, acting as a lock. For example, a binary
semaphore can be used in a multi-threaded program to limit access to a shared
resource, such as a printer, ensuring only one thread can access it at a time. This
prevents conflicts and ensures proper printing order.
Counting Semaphore: Takes non-negative values, managing multiple resources. For
instance, a counting semaphore can be used in a server application to control the
number of clients accessing a database at once, ensuring that the database is not
overloaded with requests. This helps to maintain system stability and prevent data
corruption.
6. Conclusion
Process synchronisation is essential for concurrent execution to prevent race conditions and
ensure consistency in shared resources. Mechanisms such as semaphores provide a reliable
way to control access to critical sections. By implementing proper synchronisation, systems
can achieve safe and efficient multitasking.