Monitors
A combination of data abstraction and mutual exclusion
- Invented by C.A.R. Hoare (1974)
Widely used in concurrent programming languages and libraries
- Java,
- Library pthreads,
- C#,
- etc.
Aims
Understand “classical” monitors
- Examples
- Standard variations
Understand Java monitors
- Built-in
- Library since Java 5
What is a monitor?
A collection of encapsulated procedures
- A module or a class-like structure
A single global lock to ensure mutex for all the operations in the monitor
Automatic mutex
Only one method from the monitor can run at a given time
A special type of variables called condition variables which are used for condition synchronisation
- Global to the monitor
- Associated to each one is a queue for blocked processes
- Two operations (pseudo-code):
wait(c)
, andsignal(c)
Monitor behavior
- There would be blocked threads waiting to run (sad faces) and only one happy face at a given time
The happy face can block on a conditional variable (it becomes a sad face)
It executes
wait(c)
When a thread blocks (via
wait(c)
), the monitor's lock is released
A bit like the semaphore operation
acquire
(P)wait(c)
blocks the executing thread onc
The blocked thread must release the mutex lock on the monitor
A bad face can become happy
It executes
signal(c)
When a thread gets ready to run, it first tries to acquire the lock
A bit like the semaphore operation
release
(V)signal(c)
unblocks the first process blocked onc
What happens with the mutex ? It depends on the happy face!
There are different possible semantics describing what to do (we will see two of them)
Signal and exit semantics
The caller executes
signal(c)
as the last commandIt hands over the the mutex lock on the monitor to the unblocked thread
We will use the newer version of monitors in Java (Java 5 and up)
- We will see later why!
Locks are explicit
- The lock need to be grabbed and release at the beginning and end of every method
private final Lock lock = new ReentrantLock();
Conditional variables are declared based on the monitor lock
private final Condition toBeEmpty = lock.newCondition(); private final Condition toBeFull = lock.newCondition();
Why?
- So that when a thread blocks, it knows which lock to release. Similarly, when a thread wakes up, it knows which lock to acquire.
One-slot buffer
Code for buffer,
producer,
consumer, and
the top level file
that puts everything together (note that this code assumes different
semantics than is provided in Java, and thus is not a working Java program)
Internal data structure
private E buf; private int elem = 0 ;
Writing into the buffer
public void put(E e) throws InterruptedException { lock.lock();
if (elem == 1) toBeEmpty.await() ; // Wait to be empty
buf = e; elem++ ;
toBeFull.signal() ; lock.unlock(); }
Why the use of
InterruptedException
?Observe the use of
lock
(always the same)The conditional variables are used depending on the problem
There is another operation,
signalAll()
, to awake all the blocked threads in a given condition variable
Reading from the buffer
public E get() throws InterruptedException { E result ; lock.lock(); if (elem == 0) toBeFull.await() ; // Wait to be full! result = buf ; elem-- ; toBeEmpty.signal() ; lock.unlock(); return result; }
What about the producers and consumers?
We show it only for one producer and one consumer
Similar to the version with semaphores, only that we tell the producer to use the class
BufferMonOne
public ProducerM(BufferMonOne
b, int p, int sp) public ConsumerM(BufferMonOne
b, int c, int sp)
N-slot buffer
Code for buffer,
producer,
consumer, and
the top level file
that puts everything together (similarly to previous example, this code makes assumptions that are not true for the environment provided by Java)
Remember how was the generalization for semaphores?
Two extra semaphores to protect
front
andrear
Here, we do not need that. All the data structures to be thread-safe are inside the monitor
private E[] buf; private int elem = 0 ; private int S ; private int front ; private int rear ;
Writing into the buffer
public void put(E e) throws InterruptedException { lock.lock(); if (elem == S) toBeEmpty.await() ; buf[front] = e; front = (front+1)%S ; elem++ ; toBeFull.signal() ; lock.unlock(); }
Reading from the buffer
public E get() throws InterruptedException { E result ; lock.lock(); if (elem == 0) toBeFull.await() ; result = buf[rear] ; rear = (rear+1)%S ; elem-- ; toBeEmpty.signal() ; lock.unlock(); return result; }
Does it work the same as the semaphore version?
Only one mutex
Threads wait at the condition variables
Semaphores Vs. Monitors
Semaphores
- Efficient
- Easy to implement
Monitors
Can monitors implement semaphores?
- Yes!
- Important theoretical question
- An illustrative example, but not normal practice implementing a low-level language construct in a high-level language is not normally a good idea
Can semaphores implement monitors?
- Yes!
Whatever you can write with semaphores, you can write it with monitors
- Equally expressive
Signaling Disciplines
So far we have looked at the Signal and Exit version of monitors
A signal is at the end of the method
Mutex is handed over to any woken process
Other possibilities
What if the signal is not at the end of the method?
What is the scheduling can be different?
Several possible semantics exist
- Ben-Ari: 13 in total but most are no sense
Queues
The original proposal
- The signaling thread goes to the front of the boundary queues
- The unblocked thread runs in the monitor (after
await()
)
Signal and wait
- The signaling thread goes to the end of the boundary queues
- The unblocked thread runs in the monitor (after
await()
)
Signal and continue (used by Java!)
- The signaling thread continues running until the method is over
- The unblocked thread joins the end of the boundary queue
Signal and continue
- Signaler S continues while unblocked thread W joins the boundary queue
Pattern so far
One thread W is blocked on the conditional variable
CV
if (cond) { CV.await() ; } // Rest of the code
One thread S, running in the monitor, executes
// Make cond false CV.signal() ; // Rest of the code
The condition
cond
might change when the thread resumes execution afterawait()
!
The condition might be changed by a thread in the boundary queue ready to enter before the unblocked thread
The condition might be stronger than the one represented by the condition variable
- Catch-all condition variable may be used
Solution to this problem?
Rechecked the condition every time
We need to change our programming style
- From
public void put(E e) throws InterruptedException { lock.lock(); if (elem == S) toBeEmpty.await() ; // Rest of the code lock.unlock(); } public E get() throws InterruptedException { E result ; lock.lock(); if (elem == 0) toBeFull.await() ; // Rest of the code lock.unlock(); return result; }
- To
public void put(E e) throws InterruptedException { lock.lock(); while (elem == S) toBeEmpty.await() ; // Rest of the code lock.unlock(); } public E get() throws InterruptedException { E result ; lock.lock(); while (elem == 0) toBeFull.await() ; // Rest of the code lock.unlock(); return result; }
Is it that horrible this signal discipline?
Probably, but it is widely used (e.g. Unix, phthreads, Java)
Why?
- Easier to implement
- Has simpler formal semantics
- Allows for flexible conditions
Can we avoid the while-loops if we change the signaling discipline, e.g., signal and exit?
Not in Java or pthreads!
Spurious wake-ups
Interrupting threads
We have seen the exception
InterruptedException
alreadyMethod call
thread.interrupt()
sets a flag known as interrupt statusIt will immediately wake up the thread if it tries to block/sleep
Threads can check the flag by calling
interrupted()
Difficult to use safely as a programming primitive
It can leave objects in hard-to-predict states
Your program should finish as part of its normal behavior
Nevertheless, it is very useful to finalize threads even tough they might be in a blocked state
public void run() { try { while (!interrupted()) //Do some work here } catch (InterruptedException e) {} } public void shutdown() throws InterruptedException { interrupt(); }
Here, another example that shows that interrupted threads are woken up if they were blocked
Exceptions discipline
- What if the thread is interrupted?
public void put(E e) throws InterruptedException { lock.lock(); while (elem == S) toBeEmpty.await(); buf[front] = e; front = (front+1)%S; elem++; toBeFull.signal(); lock.unlock(); }
- Ensuring that the lock is not left locked forever (avoiding deadlock)
public void put(E e) throws InterruptedException { lock.lock(); try { while (elem == S) toBeEmpty.await(); buf[front] = e; front = (front+1)%S; elem++; toBeFull.signal(); } finally { lock.unlock(); } }
Fairness of monitors
Java 5 monitors can guarantee fairness for the boundary queue
- It behaves as a queue
Monitors have several queues that might interact
Boundary queue
Queues associated with synchronization variables
Could it be that fairness is not fulfilled?
Yes!
A signal "can be stolen" from a blocked thread
An example
One thread waiting 10 years for a conditional to happen (blocked on conditional variable
c
)One thread waiting 10 seconds to get into the monitor
The running thread makes that condition to become true and signals
c
The blocked thread is added to the end of the boundary queue
- The thread waiting for 10 seconds gets into the monitor and change the condition back to false
- Now the blocked thread for 10 years get into the monitor, but it will block again!
What should we do about fairness?
It is generally ignored when using monitors
You can program your way around
- An additional queue (for each synchronization variable) which unifies the boundary and the synchronization queues
Monitors (< Java 5)
Less fairness guarantees than in Java 5
No fairness for the boundary queue
Signals can be stolen (similar as monitors in Java 5)
Only one conditional variable per monitor
Unlocking is automatic on exception
synchronized public void put(E e) throws InterruptedException { while (elem == S) wait(); buf[front] = e; front = (front+1)%S ; elem++; notifyAll(); }
Barrier synchronization
Common concurrency pattern
N processes must wait for the slowest before continuing with the next activity
Widely used in parallel programming
We will implement it as a monitor
public class Barrier { private final Lock lock = new ReentrantLock(); private final Condition AllOnTheBarrier = lock.newCondition(); private int members ; private int sofar ; public Barrier (int total) { sofar = 0 ; members = total ; } public void sync() throws InterruptedException { lock.lock(); sofar++ ; if (sofar == members) AllOnTheBarrier.signalAll() ; else while (sofar < members) AllOnTheBarrier.await() ; sofar-- ; lock.unlock(); } }
Does it work?
- Here, the code for the monitor, threads using it, and the top level file
What is the problem?
We needed to add the while-loop to deal with spurious wake-ups
The while-loop might keep blocking threads when it times for them to leave the barrier
The last thread arrives to the barrier and signals to all the blocked thread
The thread that signaled all continues running and the first blocked awaken thread executes
sofar--
(exit the barrier)- Since
sofar < members
(recall it was executedsofar--
), the other threads to exit the barrier will block due to the linewhile (sofar < members)
- Since
What if we do not use
sofar--
?
public void sync() throws InterruptedException { lock.lock(); sofar++ ; if (sofar == members) { AllOnTheBarrier.signalAll() ; sofar = 0 ; } else while (sofar < members) AllOnTheBarrier.await() ; lock.unlock(); }
- Still problematic, since the guard of all the thread in the barriers (except for the last one to arrive) is going to block again
Solution?
Have an counter that says in which instance of the barrier is a thread blocked
For instance, it is in the first barrier, the second one, the third, etc?
A global variable which counts the current instance of the barrier (
turn
) and a local variable which stores to which instance the call tosync()
correspondspublic void sync() throws InterruptedException { int myturn ; // To which instance correspond this call lock.lock(); myturn = turn ; sofar++ ; if (sofar == members) { AllOnTheBarrier.signalAll() ; turn++ ; sofar = 0 ; } else while (sofar < members && turn == myturn) AllOnTheBarrier.await() ; lock.unlock(); }
Here, the code for the monitor, threads using it, and the top level file
What if turn overflows?
turn = (turn+1)%2
Summary
Allow better structured programming
As expressive as semaphores
Various monitor signaling semantics
- Signal and continue (Java)
Java monitors
- Expressive, though complex
- Programming effort to get fairness
Interrupting a (blocked) thread
Synchronization barrier
Next time
More classic problems
More Java monitors