0% found this document useful (0 votes)
8 views27 pages

Syncronization

study related

Uploaded by

sajay676767
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views27 pages

Syncronization

study related

Uploaded by

sajay676767
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 27

Classical Synchronization problems: case study Rachna J

CASE STUDY : CLASSICAL SYNCHRONIZATION PROBLEMS

BACHELOR OF SCIENCE IN INFORMATION TECHNOLOGY


DONE BY
RACHNA J
22107112

DEPARTMENT OF INFORMATION TECHNOLOGY


SRI RAMAKRISHNA COLLEGE OF ARTS & SCIENCE
(FORMERLY SNR SON’S COLLEGE)
[AN AUTONOMOUS INSTITUTION]
(AFFILIATED TO BHARATIAR UNIVERSITY)
COIMBATORE – 641006

JUNE 2024
SRI RAMAKRISHNA COLLEGE OF ARTS & SCIENCE
1
Classical Synchronization problems: case study Rachna J

Case study : Classical synchronization problems


Introduction

Synchronization in concurrent computing is crucial for ensuring that multiple


processes or threads can operate safely and efficiently when accessing shared resources. The
need for effective synchronization becomes apparent when considering the potential for race
conditions, deadlocks, and resource contention that can arise in such environments. Classical
synchronization problems serve as foundational examples that highlight these challenges and
the methods used to address them.

This case study explores several well-known synchronization problems—


Bounded-Buffer (or Producer-Consumer), Dining-Philosophers, Readers and Writers, and the
Sleeping Barber problem. Each of these problems presents unique scenarios requiring precise
control over the execution order and access permissions of concurrent processes.

We will delve into the traditional semaphore-based solutions to these problems.


Semaphores, introduced by Dijkstra in the 1960s, are among the oldest and most fundamental
synchronization primitives. They provide a straightforward mechanism for controlling access
to shared resources, making them ideal for illustrating the core concepts of process
synchronization.

Through this case study, we aim to provide a comprehensive understanding of


these classical problems and demonstrate how semaphores can be effectively utilized to solve
them. Additionally, we will discuss potential real-world applications and the practicality of
employing mutex locks as an alternative to binary semaphores in modern implementations. By
examining these synchronization problems and their solutions, we can gain valuable insights
into the broader field of concurrency control and resource management in computing
systems.we will examine several classical synchronization problems, which serve as
representative examples of a broader category of concurrency-control issues. Our solutions to
these problems will utilize semaphores for synchronization, as this is the traditional method of
presenting such solutions. However, practical implementations of these solutions may employ
mutex locks instead of binary semaphores.

2
Classical Synchronization problems: case study Rachna J

Synchronization Problems

The following synchronization problems are considered classical and are often used
to test new synchronization schemes:

1. Bounded-buffer (or Producer-Consumer) Problem

2. Dining-Philosophers Problem

3. Readers and Writers Problem

4. Sleeping Barber Problem

1. Bounded-Buffer (or Producer-Consumer) Problem

The Bounded Buffer problem, also known as the Producer-Consumer problem,


involves two counting semaphores, "full" and "empty," to keep track of the number of full and
empty buffers, respectively. Producers create products and consumers use these products, with
each accessing one container at a time.

3
Classical Synchronization problems: case study Rachna J

Producer-Consumer Solution Using Semaphores in Java | Set 2

The producer–consumer problem, also known as the bounded-buffer problem, is a


classic example of a multi-process synchronization issue. It involves two processes, the
producer and the consumer, which share a common, fixed-size buffer used as a queue.

Producer: Generates data, places it into the buffer, and then repeats the process.

Consumer: Consumes the data from the buffer, one piece at a time.

Problem: Ensure that the producer does not add data to a full buffer and that the consumer
does not remove data from an empty buffer.

Solution: The producer either goes to sleep or discards data if the buffer is full. When the
consumer removes an item from the buffer, it notifies the producer to start filling the buffer
again. Similarly, the consumer can sleep if the buffer is empty. When the producer adds data to
the buffer, it wakes up the sleeping consumer. An inadequate solution may lead to a deadlock
where both processes wait to be awakened.

The following solution consists of four classes:

- `Q`: The queue that is synchronized.

- `Producer`: The threaded object producing queue entries.

- `Consumer`: The threaded object consuming queue entries.

- `PC`: The driver class that creates the single `Q`, `Producer`, and `Consumer`.

import java.util.concurrent.Semaphore;

class Q {
// An item
int item;

// semCon initialized with 0 permits to ensure put() executes first


static Semaphore semCon = new Semaphore(0);

4
Classical Synchronization problems: case study Rachna J

static Semaphore semProd = new Semaphore(1);

// To get an item from the buffer


void get() {
try {
// Before the consumer can consume an item, it must acquire a permit from semCon
semCon.acquire();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}

// Consumer consuming an item


System.out.println("Consumer consumed item: " + item);

// After consuming the item, release semProd to notify the producer


semProd.release();
}

// To put an item in the buffer


void put(int item) {
try {
// Before the producer can produce an item, it must acquire a permit from semProd
semProd.acquire();
} catch (InterruptedException e) {
System.out.println("InterruptedException caught");
}

// Producer producing an item


this.item = item;

System.out.println("Producer produced item: " + item);

5
Classical Synchronization problems: case study Rachna J

// After producing the item, release semCon to notify the consumer


semCon.release();
}
}
// Producer class
class Producer implements Runnable {
Q q;
Producer(Q q) {
this.q = q;
new Thread(this, "Producer").start();
}

public void run() {


for (int i = 0; i < 5; i++) {
// Producer puts items
q.put(i);
}
}
}

// Consumer class
class Consumer implements Runnable {
Q q;
Consumer(Q q) {
this.q = q;
new Thread(this, "Consumer").start();
}

public void run() {


for (int i = 0; i < 5; i++) {
// Consumer gets items
q.get();

6
Classical Synchronization problems: case study Rachna J

}
}
}

// Driver class
class PC {
public static void main(String args[]) {
// Creating buffer queue
Q q = new Q();

// Starting consumer thread


new Consumer(q);

// Starting producer thread


new Producer(q);
}
}

Output:

Producer produced item: 0


Consumer consumed item: 0
Producer produced item: 1
Consumer consumed item: 1
Producer produced item: 2
Consumer consumed item: 2
Producer produced item: 3
Consumer consumed item: 3
Producer produced item: 4
Consumer consumed item: 4

7
Classical Synchronization problems: case study Rachna J

Explanation:

The calls to `put()` and `get()` are synchronized, ensuring that each call to `put()` is
followed by a call to `get()` with no items missed. Without semaphores, multiple calls to `put()`
could occur without matching `get()` calls, resulting in missed items. The sequencing of `put()`
and `get()` calls is managed by two semaphores: `semProd` and `semCon`.

- Before `put()` can produce an item, it must acquire a permit from `semProd`. After producing
the item, it releases `semCon`.

- Before `get()` can consume an item, it must acquire a permit from `semCon`. After consuming
the item, it releases `semProd`.

This “give and take” mechanism ensures that each `put()` call is followed by a `get()` call.
Additionally, `semCon` is initialized with no available permits, ensuring that `put()` executes
first. The ability to set the initial synchronization state is one of the more powerful aspects of
a semaphore.

2. Dining-Philosophers Problem

The Dining Philosophers Problem describes K philosophers seated around a circular


table with one chopstick between each pair of philosophers. A philosopher may eat if they can
pick up the two chopsticks adjacent to them. Each chopstick can only be picked up by one
philosopher at a time, preventing deadlock and starvation while ensuring the allocation of
limited resources to a group of processes.

8
Classical Synchronization problems: case study Rachna J

Dining Philosopher Problem Using Semaphores

The Dining Philosopher Problem describes K philosophers seated around a circular


table with one chopstick between each pair of philosophers. A philosopher may eat if they can
pick up the two chopsticks adjacent to them, but each chopstick can only be picked up by one
of its adjacent philosophers at a time.

In this solution, semaphores are used to control access to the chopsticks, preventing
deadlock and ensuring that no two adjacent philosophers eat simultaneously.

Pseudocode:

process P[i]

while true do

{ THINK;

PICKUP(CHOPSTICK[i], CHOPSTICK[i+1 mod 5]);

EAT;

PUTDOWN(CHOPSTICK[i], CHOPSTICK[i+1 mod 5])

9
Classical Synchronization problems: case study Rachna J

Philosophers have three states: THINKING, HUNGRY, and EATING. Two semaphores are
used: `mutex` to ensure mutual exclusion and an array of semaphores for the philosophers to
control their behavior.

Solution in C++:

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

#define N 5
#define THINKING 2
#define HUNGRY 1
#define EATING 0
#define LEFT (phnum + 4) % N
#define RIGHT (phnum + 1) % N
int state[N];
int phil[N] = { 0, 1, 2, 3, 4 };

std::mutex mutex;
std::condition_variable S[N];

void test(int phnum) {


if (state[phnum] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING)
{
state[phnum] = EATING;
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
std::cout << "Philosopher " << phnum + 1 << " takes fork " << LEFT + 1 << " and " <<
phnum + 1 << std::endl;
std::cout << "Philosopher " << phnum + 1 << " is Eating" << std::endl;
S[phnum].notify_all();
}
}

10
Classical Synchronization problems: case study Rachna J

void take_fork(int phnum) {


std::unique_lock<std::mutex> lock(mutex);
state[phnum] = HUNGRY;
std::cout << "Philosopher " << phnum + 1 << " is Hungry" << std::endl;
test(phnum);
S[phnum].wait(lock);
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}

void put_fork(int phnum) {


std::unique_lock<std::mutex> lock(mutex);
state[phnum] = THINKING;
std::cout << "Philosopher " << phnum + 1 << " putting fork " << LEFT + 1 << " and " <<
phnum + 1 << " down" << std::endl;
std::cout << "Philosopher " << phnum + 1 << " is thinking" << std::endl;
test(LEFT);
test(RIGHT);
}

void philosopher(int num) {


while (true) {
take_fork(num);
put_fork(num);
}
}

int main() {
std::thread threads[N];
for (int i = 0; i < N; i++) {
threads[i] = std::thread(philosopher, i);
std::cout << "Philosopher " << i + 1 << " is thinking" << std::endl;
}

11
Classical Synchronization problems: case study Rachna J

for (int i = 0; i < N; i++)


threads[i].join();
return 0;
}

Output:

Philosopher 1 is thinking
Philosopher 2 is thinking
Philosopher 3 is thinking
Philosopher 4 is thinking
Philosopher 5 is thinking
Philosopher 1 is Hungry
Philosopher 1 takes fork 5 and 1
Philosopher 1 is Eating
Philosopher 2 is Hungry
Philosopher 3 is Hungry
Philosopher 4 is Hungry
Philosopher 5 is Hungry
Philosopher 1 putting fork 5 and 1 down
Philosopher 1 is thinking
Philosopher 2 takes fork 1 and 2
Philosopher 2 is Eating
Philosopher 3 takes fork 2 and 3
Philosopher 3 is Eating
Philosopher 2 putting fork 1 and 2 down
Philosopher 2 is thinking
Philosopher 3 putting fork 2 and 3 down
Philosopher 3 is thinking

12
Classical Synchronization problems: case study Rachna J

Solution in Python:

import threading
import time
import random

# Define the number of philosophers and forks


num_philosophers = 5
num_forks = num_philosophers

# Define semaphores for the forks and the mutex


forks = [threading.Semaphore(1) for i in range(num_forks)]
mutex = threading.Semaphore(1)

# Define the philosopher thread function


def philosopher(index):
while True:
print(f"Philosopher {index} is thinking...")
time.sleep(random.randint(1, 5))

mutex.acquire()

left_fork_index = index
right_fork_index = (index + 1) % num_forks

forks[left_fork_index].acquire()
forks[right_fork_index].acquire()

mutex.release()

print(f"Philosopher {index} is eating...")


time.sleep(random.randint(1, 5))

13
Classical Synchronization problems: case study Rachna J

forks[left_fork_index].release()
forks[right_fork_index].release()

# Create a thread for each philosopher


philosopher_threads = []
for i in range(num_philosophers):
philosopher_threads.append(threading.Thread(target=philosopher, args=(i,)))

# Start the philosopher threads


for thread in philosopher_threads:
thread.start()

# Wait for the philosopher threads to complete


for thread in philosopher_threads:
thread.join()

Output:
Philosopher 0 is thinking...
Philosopher 1 is thinking...
Philosopher 2 is thinking...
Philosopher 3 is thinking...
Philosopher 4 is thinking...
Philosopher 1 is eating...
Philosopher 2 is eating...
Philosopher 0 is eating...
Philosopher 4 is eating...
Philosopher 3 is eating...
Philosopher 1 is thinking...
Philosopher 2 is thinking...
Philosopher 3 is thinking...
Philosopher 4 is thinking...
Philosopher 0 is thinking...

14
Classical Synchronization problems: case study Rachna J

Philosopher 3 is eating...
Philosopher 4 is eating...
Philosopher 0 is eating...
Philosopher 1 is eating...
Philosopher 2 is eating...
Philosopher 0 is thinking...
Philosopher 1 is thinking...
Philosopher 2 is thinking...
Philosopher 3 is thinking...
Philosopher 4 is thinking...

Conclusion

By using semaphores to control access to the forks, the Dining Philosopher Problem
is solved, ensuring that philosophers can eat without deadlock or starvation. The `mutex`
semaphore ensures mutual exclusion while philosophers attempt to pick up forks, and the fork
semaphores ensure that a philosopher can only eat if both adjacent forks are available.

3. Readers and Writers Problem

In the Readers and Writers Problem, a database is shared among several concurrent
processes. Some processes only read the database (readers), while others read and write
(writers). Key constraints include:

15
Classical Synchronization problems: case study Rachna J

- One set of data is shared among several processes.

- Only one writer can write at a time.

- If a process is writing, no other process can read.

- If at least one reader is reading, no other process can write.

- Readers can only read and not write.

Readers-Writers Problem: Readers Preference Solution

The Readers-Writers Problem is a classical synchronization problem that describes


a scenario where multiple processes need to access a shared resource (a file). The key
constraints are:

1. If a writer is writing, no other writer or reader should access the file.

2. If a reader is reading, other readers may also read, but no writer can write.

Problem Parameters:

1. Shared Data: One set of data shared among multiple processes.

2. Write Access: Only one writer can write at a time.

3. Read Access: Multiple readers can read simultaneously.

4. No Concurrent Access: If a process is writing, no other process can read or write.

Solution: Reader Preference

In this solution, readers are given preference over writers. This means that if readers are already
reading, a writer must wait until all readers have finished.

16
Classical Synchronization problems: case study Rachna J

Semaphore and Variable Definitions:

- `semaphore mutex`: Ensures mutual exclusion when updating the reader count (`readcnt`).

- `semaphore wrt`: Ensures mutual exclusion for writers and prevents writers when there are
readers.

- `int readcnt`: Counts the number of readers currently in the critical section. Initially set to 0.

Semaphore Functions:

- `wait()`: Decrements the semaphore value.

- `signal()`: Increments the semaphore value.

Writer Process:

The writer requests access to the critical section. If allowed (`wait(wrt)` succeeds),
it performs the write and then exits.

do {

wait(wrt); // Request critical section

// Perform writing

signal(wrt); // Release critical section

} while (true);

Reader Process:

The reader requests access to the critical section. If allowed, it increments the reader
count. If this is the first reader, it locks the `wrt` semaphore to prevent writers from entering.
After reading, it decrements the reader count and releases the `wrt` semaphore if there are no
more readers.

17
Classical Synchronization problems: case study Rachna J

do {
wait(mutex); // Request access to update readcnt
readcnt++; // Increment reader count
if (readcnt == 1)
wait(wrt); // First reader locks writers
signal(mutex); // Allow other readers to enter

// Perform reading

wait(mutex); // Request access to update readcnt


readcnt--; // Decrement reader count
if (readcnt == 0)
signal(wrt); // Last reader releases writers
signal(mutex); // Allow other readers to enter

} while (true);

Complete Implementation:

Reader Process:
int rc = 0;
semaphore mutex = 1;
semaphore db = 1;
void Reader(void) {
while (true) {
down(mutex);
rc = rc + 1;
if (rc == 1)
down(db); // First reader locks writers
up(mutex);
// Reading section
down(mutex);

18
Classical Synchronization problems: case study Rachna J

rc = rc - 1;
if (rc == 0)
up(db); // Last reader releases writers
up(mutex);
// Process data
}
}

Writer Process:

void Writer(void) {
while (true) {
down(db); // Request critical section
// Writing section
up(db); // Release critical section
}
}

Explanation:

1. Reader Process:

- When a reader enters, it increments `readcnt`.

- If it is the first reader, it locks `wrt` to prevent writers from entering.

- Other readers can continue reading simultaneously.

- When a reader exits, it decrements `readcnt`.

- If it is the last reader, it releases `wrt` to allow writers.

2. Writer Process:

- Writers wait for `wrt` to become available.

- Once allowed, a writer performs its write and then releases `wrt`.

19
Classical Synchronization problems: case study Rachna J

This solution ensures that readers are prioritized, allowing multiple readers to access
the shared resource simultaneously, while writers must wait until there are no readers. This
prevents writers from being starved indefinitely, as they will eventually get access when there
are no readers.

4. Sleeping Barber Problem

The Sleeping Barber Problem involves a barbershop with one barber, one barber
chair, and N waiting chairs. When there are no customers, the barber sleeps in the barber chair
and must be awakened when a customer arrives. While the barber is cutting hair, new customers
either take an empty seat to wait or leave if no seats are available.

These classical problems highlight the challenges and solutions in managing concurrent
processes and resources effectively.

Sleeping Barber Problem in Process Synchronization

The Sleeping Barber problem is a classic synchronization problem that illustrates


issues in a concurrent system. It involves a barber shop with one barber, a barber chair, and a
number of waiting chairs for customers. The problem is to coordinate the actions of the barber
and customers to avoid deadlock and starvation.

20
Classical Synchronization problems: case study Rachna J

Problem Description:

1. Barber Shop Setup:

- One barber

- One barber chair

- N waiting chairs for customers

2. Behavior:

- The barber sleeps if there are no customers.

- Customers arrive randomly. If a chair is available, they wait; if not, they leave.

- The barber checks for waiting customers after finishing a haircut. If there are, he cuts the
next customer’s hair; if not, he goes back to sleep.

Solution Using Semaphores:

To solve the Sleeping Barber problem, three semaphores are used:

- `Semaphore customers`: Counts the number of customers in the waiting room.

- `Semaphore barber`: Indicates if the barber is available (0 or 1).

- `Mutex seats`: Ensures mutual exclusion when accessing the number of available seats.

Implementation in Pseudocode:

#### Barber Process:


Semaphore customers = 0;
Semaphore barber = 0;
Mutex seats = 1;
int freeSeats = N;

Barber {
while (true) {

21
Classical Synchronization problems: case study Rachna J

// Wait for a customer (sleep)


down(customers);

// Mutex to protect the number of available seats


down(seats);

// One chair gets free


freeSeats++;

// Signal the barber to cut hair


up(barber);

// Release the mutex on the chair


up(seats);
// Barber is cutting hair
}
}

#### Customer Process:


Customer {
while (true) {
// Protect seats to ensure only one customer can sit at a time
down(seats);

if (freeSeats > 0) {
// Sitting down
freeSeats--;

// Notify the barber


up(customers);

// Release the lock

22
Classical Synchronization problems: case study Rachna J

up(seats);

// Wait if the barber is busy


down(barber);

// Customer is getting a haircut


} else {
// Release the lock
up(seats);

// Customer leaves
}
}
}

Python Implementation:

Below is a Python implementation using the `threading` module.

import threading
import time
import random

# Maximum number of customers


MAX_CUSTOMERS = 5
# Number of chairs in the waiting room
NUM_CHAIRS = 3

# Semaphores for the barber, customers, and mutex


barber_semaphore = threading.Semaphore(0)
customer_semaphore = threading.Semaphore(0)
mutex = threading.Semaphore(1)

23
Classical Synchronization problems: case study Rachna J

# List to keep track of waiting customers


waiting_customers = []

# Barber thread function


def barber():
while True:
print("The barber is sleeping...")
barber_semaphore.acquire()
mutex.acquire()
if len(waiting_customers) > 0:
customer = waiting_customers.pop(0)
print(f"The barber is cutting hair for customer {customer}")
mutex.release()
time.sleep(random.randint(1, 5))
print(f"The barber has finished cutting hair for customer {customer}")
customer_semaphore.release()
else:
mutex.release()

# Customer thread function


def customer(index):
global waiting_customers
time.sleep(random.randint(1, 5))
mutex.acquire()
if len(waiting_customers) < NUM_CHAIRS:
waiting_customers.append(index)
print(f"Customer {index} is waiting in the waiting room")
mutex.release()
barber_semaphore.release()
customer_semaphore.acquire()
print(f"Customer {index} has finished getting a haircut")

24
Classical Synchronization problems: case study Rachna J

else:
print(f"Customer {index} is leaving because the waiting room is full")
mutex.release()

# Create a thread for the barber


barber_thread = threading.Thread(target=barber)

# Create threads for each customer


customer_threads = []
for i in range(MAX_CUSTOMERS):
customer_threads.append(threading.Thread(target=customer, args=(i,)))

# Start the barber and customer threads


barber_thread.start()
for thread in customer_threads:
thread.start()

# Wait for all customers to finish


for thread in customer_threads:
thread.join()

Explanation:

1. Barber Process:

- Sleeps initially, waiting for customers.

- Wakes up when a customer arrives (`barber_semaphore` is released).

- Cuts the customer’s hair and goes back to sleep if no customers are waiting.

25
Classical Synchronization problems: case study Rachna J

2. Customer Process:

- Arrives randomly.

- Takes a seat if available; otherwise, leaves.

- Notifies the barber if seated.

- Waits for the barber to finish the haircut.

This solution ensures efficient resource use, prevents race conditions, and guarantees
fairness. Proper semaphore use prevents deadlocks and ensures mutual exclusion.

Conclusion

In this case study, we have examined several classical synchronization problems and
their solutions using semaphores. These problems—the Bounded-Buffer (or Producer-
Consumer), Dining-Philosophers, Readers and Writers, and the Sleeping Barber—serve as
fundamental examples to illustrate the challenges and intricacies of process synchronization in
concurrent computing environments.

Semaphores provide a robust mechanism for controlling access to shared resources


and coordinating the actions of multiple processes. Through the implementation of semaphore-
based solutions, we have demonstrated how to effectively manage resource contention, prevent
deadlocks, and ensure fair access to resources. These solutions highlight the importance of
carefully designing synchronization mechanisms to maintain system stability and efficiency.

While semaphores offer a traditional and powerful approach to synchronization,


practical implementations in modern systems often employ mutex locks and other
synchronization primitives. These alternatives can provide more granular control and may be
better suited to specific use cases, offering advantages such as reduced complexity and
improved performance.

26
Classical Synchronization problems: case study Rachna J

The exploration of these classical problems underscores the critical role of


synchronization in concurrent programming. By understanding and applying these
fundamental concepts, developers can design systems that are robust, efficient, and capable of
handling the complexities of concurrent operations. As computing continues to evolve, the
principles and techniques discussed in this case study will remain essential for addressing the
challenges of synchronization and concurrency control.

Frequently asked questions

1. What is the purpose of employing semaphores or mutex locks to synchronize in these


problems?

Semaphores and mutex locks are synchronization primitives that control access to shared
resources among several processes or threads. They ensure that several processes or threads
can access shared resources in a regulated and orderly manner, thereby avoiding conflicts and
maintaining data integrity.

2. Can the solutions to these synchronization issues be addressed with mutex locks
rather than semaphores?

Yes, solutions to these synchronization issues can be achieved utilizing semaphores or mutex
locks. Both semaphores and mutex locks can do comparable synchronization tasks. The
decision between them is often determined by the problem's specific requirements as well as
the programming language or framework in use.

3. Do these synchronization issues only affect multi-threaded or multi-process


environments?

Yes, these synchronization issues are common in multi-threaded or multi-process contexts


where several entities must access shared resources simultaneously. In single-threaded or
single-process contexts, these synchronization issues may be unnecessary or have simpler
solutions.

27

You might also like