0% found this document useful (0 votes)
148 views

Chapter 7. Synchronization Examples

The document discusses synchronization examples including classical problems like the bounded-buffer problem, readers-writers problem, and dining philosophers problem. It then covers synchronization techniques in Linux like semaphores, atomic integers, spinlocks, and mutex locks. Classical problems are solved using semaphores while Linux provides its own kernel synchronization primitives.
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)
148 views

Chapter 7. Synchronization Examples

The document discusses synchronization examples including classical problems like the bounded-buffer problem, readers-writers problem, and dining philosophers problem. It then covers synchronization techniques in Linux like semaphores, atomic integers, spinlocks, and mutex locks. Classical problems are solved using semaphores while Linux provides its own kernel synchronization primitives.
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/ 39

COMPUTER ORGANIZATION AND DESIGN

The Hardware/Software Interface

Chapter 7
Synchronization Examples

Yunmin Go

School of CSEE
Agenda
◼ Classic Problems of Synchronization
◼ Synchronization within the Kernel
◼ POSIX Synchronization
◼ Synchronization in Java
◼ Alternative Approaches

Synchronization Examples - 2
Classical Problems of Synchronization
◼ The bounded-buffer problem

◼ The readers-writers problem

◼ The dining-philosophers problem

Synchronization Examples - 3
The Bounded-Buffer Problem
Buffer Consumer
Producer info. info.
(Size n)

◼ If the buffer is full, the producer must wait until the consumer deletes
an item
◼ Producer needs an empty space
◼ # of empty slot is represented by a semaphore empty
◼ If the buffer is empty, the consumer must wait until the producer adds
an item
◼ Consumer needs an item
◼ # of item is represented by a semaphore full
Synchronization Examples - 4
The Bounded-Buffer Problem
◼ Producer-consumer problem with bounded buffer
◼ Semaphores: full = 0, empty = n, mutex = 1;
◼ Producer ◼ Consumer

while (true) { while (true) {


... wait(full);
/* produce an item in next_produced */ wait(mutex);
... ...
wait(empty); /* remove an item from buffer to next_consumed */
wait(mutex); ...
... signal(mutex);
/* add next produced to the buffer */ signal(empty);
... ...
signal(mutex); /* consume the item in next consumed */
signal(full); ...
} }

Synchronization Examples - 5
The Readers-Writers Problem
◼ There are multiple readers and writers to access a shared data
◼ Readers can access database simultaneously.
◼ When a writer is accessing the shared data, no other thread can access it.

Writer Reader

Writer Reader
Shared Data

Writer Reader

Synchronization Examples - 6
The Readers-Writers Problem
◼ Behavior of a writer
◼ If a thread is in the critical section, all writers must wait
◼ The writer can enter the critical section only when no thread is in its critical
section
◼ It should prevent all threads from entering the critical section
◼ Behavior of a reader
◼ If no writer is in its critical section, the reader can enter the critical section
◼ Otherwise, the reader should wait until the writer leaves the critical section
◼ When a reader is in its critical section, any reader can enter the critical
section, but no writer can
◼ Condition for the first reader is different from the following readers

Synchronization Examples - 7
The Readers-Writers Problem
◼ Shared data ◼ Reader
◼ Semaphore: mutex=1, rw_mutex=1; while (true) {
wait(mutex);
◼ int read_count = 0; read_count++;
◼ # of readers in critical section if (read_count == 1)
wait(rw_mutex);
signal(mutex);
◼ Writer ...
/* reading is performed */
while (true) { ...
wait(rw_mutex); wait(mutex);
... read count--;
/* writing is performed */ if (read_count == 0)
... signal(rw_mutex);
signal(rw_mutex); signal(mutex);
} }

Synchronization Examples - 8
The Dining Philosophers Problem
◼ Problem definition
◼ 5 philosophers sitting on a circular table
◼ Thinking or eating
◼ 5 bowls, 5 single chopsticks
◼ No interaction with colleagues
◼ To eat, the philosopher should pick up two chopsticks
closest to her
◼ A philosopher can pick up only one chopstick at a time
◼ When she finish eating, she release chopsticks
◼ Solution should be deadlock-free and starvation-free

Synchronization Examples - 9
The Dining Philosophers Problem
◼ Shared data
◼ Bowl of rice (data set)
◼ Semaphore chopstick[5] initialized to 1
◼ A possible solution, but deadlock can occur
while (true){
wait(chopstick[i]); // pick up left chopstick
wait(chopstick[(i+1) % 5]); // pick up right chopstick

/* eat for a while */

signal(chopstick[i]); // release left chopstick


signal(chopstick[(i+1) % 5]); // release right chopstick

/* think for a while */


}

Synchronization Examples - 10
The Dining Philosophers Problem
◼ Possible remedies to the deadlock problem
◼ Allow at most four philosophers to be sitting
simultaneously at the table
◼ Allow a philosopher to pick up her chopsticks only if
both chopsticks are available
◼ An odd numbered philosopher picks up first her left
chopstick and then her right chopstick, whereas an
even-numbered philosophers picks up her right
chopstick and then her left chopstick

Above solutions don’t necessarily eliminate the possibility of starvation.

Synchronization Examples - 11
The Dining Philosophers Problem
◼ Monitor solution to the dining philosophers problem
monitor DiningPhilosophers
{
enum {THINKING; HUNGRY, EATING} state[5] ;
condition self[5];
void test (int i) {
void pickup (int i) { if ((state[(i + 4) % 5] != EATING) &&
state[i] = HUNGRY; (state[i] == HUNGRY) &&
test(i); (state[(i + 1) % 5] != EATING)) {
if (state[i] != EATING) state[i] = EATING ;
self[i].wait; self[i].signal () ;
} }
}
void putdown (int i) {
state[i] = THINKING; initialization_code() {
// test left and right neighbors for (int i = 0; i < 5; i++)
test((i + 4) % 5); state[i] = THINKING;
test((i + 1) % 5); }
} }

Synchronization Examples - 12
The Dining Philosophers Problem
◼ Monitor solution to the dining philosophers problem
◼ Each philosopher i invokes the operations pickup() and putdown() in the
following sequence:

DiningPhilosophers.pickup(i);
...
/** EAT **/
...
DiningPhilosophers.putdown(i);

◼ No deadlock (deadlock-free), but starvation is possible (not starvation-free)

Synchronization Examples - 13
Agenda
◼ Classic Problems of Synchronization
◼ Synchronization within the Kernel
◼ POSIX Synchronization
◼ Synchronization in Java
◼ Alternative Approaches

Synchronization Examples - 14
Synchronization in Linux
◼ Linux:
◼ Prior to kernel Version 2.6, disables interrupts to implement short critical
sections
◼ Version 2.6 and later, fully preemptive
◼ Linux provides:
◼ Semaphores
◼ atomic integers
◼ spinlocks
◼ reader-writer versions of both
◼ On single-CPU system, spinlocks replaced by enabling and disabling
kernel preemption
Synchronization Examples - 15
Synchronization in Linux
◼ Atomic variables
◼ atomic_t is the type for atomic integer
◼ All math operations are performed without interruption
◼ Ex) atomic_t counter;
int value;

Synchronization Examples - 16
Synchronization in Linux
◼ Mutex locks
◼ int mutex_init(struct mutex *lock);
◼ Initialize the mutex
◼ int mutex_lock(struct mutex *lock);
◼ Acquire the mutex
◼ Must be invoked prior to entering a critical section
◼ int mutex_unlock(struct mutex *lock);
◼ Release the mutex
◼ Must be invoked after exiting the critical section
◼ If the mutex lock is unavailable, a task calling mutex_lock() is put into sleep state and is
awakened when the lock’s owner invokes mutex_unlock()

Synchronization Examples - 17
Synchronization in Linux
◼ Spinlocks
◼ spin_lock(), spin_unlock(), etc.
◼ Useful for short duration
◼ Inappropriate on single-processor but appropriate on multi-processor

◼ Enabling/disabling kernel preemption


◼ preempt_disable(), preempt_enable()
Single Processor Multiple Processors
Disable kernel preemption Acquire spin lock
Enable kernel preemption Release spin lock

https://www.kernel.org/doc/htmldocs/kernel-locking/apiref-mutex.html Synchronization Examples - 18


Agenda
◼ Classic Problems of Synchronization
◼ Synchronization within the Kernel
◼ POSIX Synchronization
◼ Synchronization in Java
◼ Alternative Approaches

Synchronization Examples - 19
POSIX Synchronizations
◼ POSIX API provides
◼ Mutex locks
◼ Semaphores
◼ Condition variable
◼ Widely used on UNIX, Linux, and macOS

Synchronization Examples - 20
POSIX Mutex Locks
◼ Creating and initializing the lock
#include <pthread.h>
pthread_mutex_t mutex; // global declaration

// create and initialize the mutext lock


pthread_mutex_init(&mutex, NULL); // call before first lock
or
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

◼ Acquiring and releasing the lock


pthread_mutex_lock(&mutex); // acquire the mutex lock

/* critical section */

pthread_mutex_unlock(&mutex); // release the mutex lock

https://man7.org/linux/man-pages/man3/pthread_mutex_init.3p.html
https://man7.org/linux/man-pages/man3/pthread_mutex_lock.3p.html
Synchronization Examples - 21
POSIX Semaphores
◼ Named semaphores
◼ Multiple unrelated processes can easily use a common semaphore
◼ Creating and initializing the lock
#include <semaphore.h>
sem_t *sem; // global declaration

// Create the semaphore and initialize it to 1


sem = sem_open("SEM", O_CREAT, 0666, 1);

◼ Acquiring and releasing the lock


sem_wait(sem); // acquire the semaphore

/* critical section */

sem_post(sem); // release the semaphore https://man7.org/linux/man-pages/man3/sem_open.3.html


https://man7.org/linux/man-pages/man3/sem_wait.3p.html
https://man7.org/linux/man-pages/man3/sem_post.3.html
Synchronization Examples - 22
POSIX Semaphores
◼ Unnamed semaphores
◼ Creating and initializing the lock
#include <semaphore.h>
sem_t sem; // global declaration

// Create the semaphore and initialize it to 1


sem_init(&sem, 0, 1);

◼ Acquiring and releasing the lock


sem_wait(&sem); // acquire the semaphore

/* critical section */

sem_post(&sem); // release the semaphore

https://man7.org/linux/man-pages/man3/sem_init.3.html
Synchronization Examples - 23
POSIX Condition Variables
◼ POSIX is typically used in C/C++ and these languages do not provide
a monitor
◼ POSIX condition variables are associated with a POSIX mutex lock to
provide mutual exclusion
◼ Creating and initializing
pthread_mutex_t mutex;
pthread_cond_t cond_var;

pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond_var, NULL);

Synchronization Examples - 24
POSIX Condition Variables
◼ Example
◼ Thread waiting for the condition a == b to become true
pthread_mutex_lock(&mutex);
while (a != b)
pthread_cond_wait(&cond_var, &mutex);
pthread_mutex_unlock(&mutex);

◼ Thread signaling another thread waiting on the condition variable:


pthread_mutex_lock(&mutex);
a = b;
pthread_cond_signal(&cond_var);
pthread_mutex_unlock(&mutex);

Synchronization Examples - 25
Agenda
◼ Classic Problems of Synchronization
◼ Synchronization within the Kernel
◼ POSIX Synchronization
◼ Synchronization in Java
◼ Alternative Approaches

Synchronization Examples - 26
Java Synchronization
◼ Java provides rich set of synchronization features:
◼ Java monitors
◼ Reentrant locks
◼ Semaphores
◼ Condition variables

Synchronization Examples - 27
Java Monitors
public class BoundedBuffer<E>
{
◼ Every Java object has associated private static final in BUFFER_SIZE = 5;
with it a single lock private int count, in, out;
private E[] buffer;
◼ If a method is declared as public BoundedBuffer() {
synchronized, a calling thread must count = 0;
in = 0;
own the lock for the object out = 0;
buffer = (E[]) new Object[BUFFER_SIZE];
◼ If the lock is owned by another }

thread, the calling thread must wait // Producers call this method
public synchronized void insert(E item) {
for the lock until it is released // See p.30
}
◼ Locks are released when the owning
thread exits the synchronized // Consumers call this method
public synchronized E remove() {
method // See p.30
}
}
Synchronization Examples - 28
Java Monitors
◼ A thread that tries to acquire an unavailable lock is placed in the
object’s entry set
◼ If the thread that has the lock is unable to continue, it calls wait().
◼ When a thread calls wait():
1. The thread releases the lock for the object
2. The state of the thread is set to blocked
3. The thread is placed in the wait set for the object

Synchronization Examples - 29
Java Monitors
◼ Thread can call notify() to wake up a waiting thread
◼ When a thread calls notify():
1. An arbitrary thread T is selected from the wait set
2. T is moved from the wait set to the entry set
3. Set the state of T from blocked to runnable
◼ T can now compete for the lock to check if the condition it was waiting for is

now true

Synchronization Examples - 30
Java Monitors
◼ Producer-Consumer in Java
// Producers call this method // Consumers call this method
public synchronized void insert(E item) { public synchronized E remove() {
while (count == BUFFER_SIZE) { E item;
try {
wait(); while (count == 0) {
} try {
catch (InterruptedException e) {} wait();
} }
catch (InterruptedException ie) { }
buffer[in] = item; }
in = (in + 1) % BUFFER_SIZE; item = buffer[out];
count++; out = (out + 1) % BUFFER_SIZE;
count--;
notify();
} notify();

return item;
}

Synchronization Examples - 31
Java Reentrant Locks
◼ Similar to mutex locks
◼ The finally clause ensures the lock will be released in case an
exception occurs in the try block
Lock key = new ReentrantLock();

key.lock();
try {
// critical section
}
finally {
key.unlock();
}

Synchronization Examples - 32
Java Semaphores
◼ Constructor
Semaphore(int value);

◼ Usage
Semaphore sem = new Semaphore(1);

try {
sem.acquire();
// critical section
}
catch (InterruptedException ie) { }
finally {
sem.release();
}

Synchronization Examples - 33
Java Condition Variables
◼ Condition variables are associated with an ReentrantLock
◼ Creating a condition variable using newCondition() method of
ReentrantLock:
Lock key = new ReentrantLock();
Condition condVar = key.newCondition();

◼ A thread waits by calling the await() method, and signals by calling


the signal() method

Synchronization Examples - 34
Java Condition Variables
◼ Example of Java condition variables
◼ Five threads numbered 0 .. 4
◼ Shared variable turn indicating which thread’s turn it is
◼ Thread calls doWork() when it wishes to do some work
(But it may only do work if it is their turn)
◼ If not their turn, wait
◼ If their turn, do some work for awhile …...
◼ When completed, notify the thread whose turn is next
◼ Necessary data structures:
Lock key = new ReentrantLock();
Condition[] condVar = new Condition[5];
for (int i = 0; i < 5; i++)
condVars[i] = lock.newCondition();

Synchronization Examples - 35
Java Condition Variables
◼ Example of Java condition variables
// threadNumber is the thread that wishes to do some work
public void doWork(int threadNumber) {
lock.lock();

try {
// If it’s not my turn, then wait until I’m signaled
if (threadNumber != turn)
condVars[threadNumber].await();

// Do some work for awhile ...

// Now signal to the next thread


turn = (turn + 1) % 5;
condVars[turn].signal();
}
catch (InterruptedException ie) { }
finally {
lock.unlock();
}
}
Synchronization Examples - 36
Agenda
◼ Classic Problems of Synchronization
◼ Synchronization within the Kernel
◼ POSIX Synchronization
◼ Synchronization in Java
◼ Alternative Approaches

Synchronization Examples - 37
Transactional Memory
◼ Memory transaction: a sequence of memory read-write operations
to memory that are performed atomically
◼ A transaction can be completed by adding atomic{S} which ensure
statements in S are executed atomically

<Traditional implementation> <Memory transaction-based implementation>


void update () void update ()
{ {
acquire(); atomic {
/* modify shared data */ /* modify shared data */
release(); }
} }

Synchronization Examples - 38
OpenMP
◼ OpenMP is a set of compiler directives and API that support parallel
programming
◼ The code contained within the #pragma omp critical directive is treated as
a critical section and performed atomically

void update(int value)


{
#pragma omp critical
{
count += value
}
}

Synchronization Examples - 39

You might also like