Another classic synchronisation problem
Two kinds of threads share access to a database
Readers examine the contents
Multiple readers allowed concurrently
Writers examine and modify
A writer must have mutex
Database is globally accessible
The monitor only encapsulate the access protocol
Monitor, Reader, Writer, and top-level
Code skeleton
public class RW { private final Lock lock = new ReentrantLock(); private final Condition okToRead = lock.newCondition(); private final Condition okToWrite = lock.newCondition(); private int readers = 0 ; private int writers = 0 ; public RW(){} public void startRead() { lock.lock() ; // code readers++ ; lock.unlock() ; } public void startWrite() { lock.lock() ; // code writers = 1 ; lock.unlock() ; } public void endRead() { lock.lock() ; readers-- ; //code lock.unlock() ; } public void endWrite() { lock.lock() ; writers = 0 //code lock.unlock() ; } }
When starting reading or writing
public void startRead() { lock.lock() ; while (writer == 1) okToRead.await() ; // code readers++ ; lock.unlock() ; } public void startWrite() { lock.lock() ; while (readers > 0 || writer == 1) okToWrite.await() ; // code writers = 1 ; lock.unlock() ; }
When finishing reading and writing
public void endRead() { lock.lock() ; readers-- ; if (readers == 0) okToWrite.signal() ; lock.unlock() ; } public void endWrite() { lock.lock() ; writers = 0 okToWrite.signal() ; okToRead.signalAll() ; lock.unlock() ; }
Why not signaling okToRead
in endRead
?
endRead
. Readers
only block due to one writer and that writer will wake up all the
*readers!Does it work?
Yes! It fulfills the requirements given in the first part of the lecture (mutex for writers, several readers when no writers running, etc.).
What about fairness?
Fairness
What about a continues flow of readers?
Suitable policy?
No new readers when a writer is waiting
Change turns in some way
Fairness often requires more book-keeping
Depends highly on platform
Java signalAll()
might be inevitable for condition rechecking
Monitor, Reader, Writer, and top-level program
Book-keeping the threads interested on reading / writing
private int wreaders = 0 ; private int wwriters = 0 ;
endWrite
public void endWrite() { lock.lock() ; writer = 0 ; okToRead.signalAll() ; okToWrite.signal() ; lock.unlock() ; }
endRead
(as before)
public void endRead() { lock.lock() ; readers-- ; if (readers == 0) okToWrite.signal() ; lock.unlock() ; }
startWrite
public void startWrite() throws InterruptedException { lock.lock() ; wwriters++ ; while (readers > 0 || writer == 1) okToWrite.await() ; wwriters-- ; writer = 1 ; lock.unlock() ; }
startRead
public void startRead() throws InterruptedException { lock.lock() ; wreaders++ ; while (writer == 1 || wwriters > 0) okToRead.await() ; wreaders-- ; readers++ ; lock.unlock() ; }
What happens with a writer with a continuous flow of readers?
startRead
and endRead
)What happens with a reader with a continuous flow of writers?
endWrite
) Is it fair?
Well, it is not an easy question to answer.
It is not a fair solution!
This is due to the signal and continue discipline
Imagine a reader thread waiting 10 years to proceed and just before that the chance is taken by another one waiting for 10 seconds. The pattern can be seen here
What else could we do?
More book-keeping
We will not do it here and we will be satisfied with this almost fair solution
A controller controls access to copies of some resource
Clients make requests to take (acquire) or return (release) one resource
One condition variable (to wait till more resources are available)
private final Condition moreResources = lock.newCondition();
Internal data structures
int units = 0 ; // Counts the number of elements private Queuestore ; // Queue to store the elements
Allocation
public E allocate() throws InterruptedException { E r ; lock.lock() ; while (units == 0) moreResources.await() ; units-- ; r = store.remove() ; lock.unlock() ; return r ; }
Release
public void release(E elem) { lock.lock() ; store.add(elem) ; moreResources.signal() ; lock.unlock() ; }
Does it work?
What about asking for several resources at the same time?
Clients requiring multiple resources should not ask for resources one at a time
Clients make requests to take or return any number of the resources
A request should only succeed if there are sufficiently many resources available,
Otherwise the request must block
Allocation
public Setallocate(int n) throws InterruptedException { Set r = new TreeSet () ; lock.lock() ; while (units < n) moreResources.await() ; units-=n ; for (int i = 0 ; i < n ; i++) r.add(store.remove()) ; lock.unlock() ; return r ; }
Release
public void release(TreeSetelems) { lock.lock() ; store.addAll(elems) ; units = units + elems.size() ; moreResources.signal() ; lock.unlock() ; }
Four approaches
Ban nested calls
Release A's lock when entering B's lock
Maintain lock on A while in B
await
on B releases both locks Maintain lock on A while in B
await
on B only releases B's lock
Less concurrency
It can lead to deadlock
What about Java?
It maintains the A's lock while entering B
A await
operation only releases the lock associated
to the condition variable
A thread might block while holding locks!
Reader/Writer problem
Fairness issues
We solved some of them
Resource/allocator
Requests for a single element
Requests for multiple instances
Nested monitors calls