Chapter 7. Synchronization Examples
Chapter 7. Synchronization Examples
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
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
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
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
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);
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
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
/* critical section */
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
/* critical section */
/* critical section */
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);
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();
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();
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
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
Synchronization Examples - 39