Partial preview of the text
Download Exploring Condition Variables & Reader-Writer Problem in Monitors and more Slides Computer Numerical Control in PDF only on Docsity!
Monitors
Arvind Krishnamurthy
Spring 2004
Monitors Recap
n Monitors contain:
n “lock” for mutual exclusion
n “condition variables” for scheduling constraints
n Monitor usage:
n Obtain lock
n Perform tasks. If certain scheduling constraints are not met,
release lock and sleep till appropriate conditions are met.
n Sleeping threads are woken up by “signal” and “broadcast”
operations
n Release lock when thread exits critical section
Synchronized queue
n Rule: must hold lock when doing condition variable
operations
AddToQueue()
lock.Acquire();
put item on queue;
condition.signal();
lock.Release();
RemoveFromQueue()
lock.Acquire();
while nothing on queue
condition.wait(&lock);
// release lock; go to
// sleep; reacquire lock
remove item from queue;
lock->Release();
return item;
Mesa-style vs. Hoare-style
n Mesa-style (Nachos, most real OS):
n Signaler keeps lock, processor
n Waiter simply put on ready queue, with no special priority
(in other words, waiter may have to wait for lock again)
n Hoare-style (most theory, textbook):
n Signaler passes lock, CPU to waiter; waiter runs immediately
n Waiter gives lock, processor back to signaler when it exits critical
section or if it waits again
n For Mesa-semantics, you always need to check the
condition after wait (use “while”). For Hoare-semantics you
can change it to “if”
Monitor Summary
General template for using monitors:
lock.Acquire();
ready = 1;
signal(cond);
lock.Release();
lock.Acquire();
while (!ready) {
wait(cond);
lock.Release();
Issue 1:
n Wait = release lock; sleep; obtain lock
n “release lock + sleep” needs to be atomic
lock.Acquire();
ready = 1;
signal(cond);
lock.Release();
lock.Acquire();
while (!ready) {
lock.Release();
sleep on cond;
lock.Release();
Thread T1 Thread T
Issue 2:
n If wait does not automatically acquire the lock when it
returns, does that lead to errors?
n Is it ok for wait to be just an atomic “release lock + sleep”
lock.Acquire();
ready = 1;
signal(cond);
lock.Release();
lock.Acquire();
while (!ready) {
wait(cond);
lock.Acquire();
lock.Release();
Thread T1 Thread T
Issue 3:
n Does the waker require mutex?
ready = 1;
signal(cond);
lock.Acquire();
while (!ready) {
wait(cond);
lock.Release();
Thread T1 Thread T
Readers/writers problem
n Motivation
n shared database (e.g., bank balances / airline seats)
n Two classes of users:
n Readers --- never modify database
n Writers --- read and modify database
n Using a single lock on the database would be overly restrictive
n want many readers at the same time
n only one writer at the same time
n Constraints
n Readers can access database when no writers (Condition okToRead)
n Writers can access database when no readers or writers (Condition
okToWrite)
n Only one thread manipulates state variable at a time
Design Specification
n Reader
n wait until no writers
n access database
n check out - wake up waiting writer
n Writer
n wait until no readers or writers
n access database
n check out --- wake up waiting readers or writer
n Lock and condition variables: okToRead, okToWrite
Solving readers/writers
Reader() {
lock.Acquire();
WR++;
while (AW > 0)
okToRead.Wait(&lock);
WR--;
AR++;
lock.Release();
Access DB;
lock.Acquire();
AR--;
if (AR == 0 && WW > 0)
okToWrite.Signal(&lock);
lock.Release();
Writer() {
lock.Acquire();
WW++;
while ((AW+AR) > 0)
okToWrite.Wait(&lock);
WW --;
AW++;
lock.Release();
Access DB;
lock.Acquire();
AW--;
if (WW > 0) okToWrite.Signal(&lock);
else if (WR > 0) okToRead.Broadcast(&lock);
lock.Release();
One-way-bridge problem
n Problem definition
n a narrow light-duty bridge on a public highway
n traffic cross in one direction at a time
n at most 3 vehicles on the bridge at the same time (otherwise it will
collapse)
n Each car is represented as one thread:
OneVechicle (int direc)
ArriveBridge(direc);
… cross the bridge …
ExitBridge(direc);
Implementing Monitors
n Can we use semaphores to implement condition variables?
n Simple attempt:
Wait() { semaphore->P(); }
Signal() { semaphore->V(); }
n Solution is not relinquishing the lock:
lock.Acquire();
while (!condition)
Wait();
lock.Release();
lock.Acquire();
Signal();
lock.Release();
Second Attempt
n Use one semaphore for each condition variable
n Release the lock during wait:
Wait(Lock *lock) {
lock->Release();
semaphore->P();
lock->Acquire();
Signal() { semaphore->V(); }
n Is this solution correct?
Peek at the waiting queue
n Perform a check during signal:
Wait(Lock *lock) {
lock->Release();
semaphore->P();
lock->Acquire();
Signal()
if semaphore queue is not empty
semaphore->V();
n Well, it is cheating! But is it correct?
Implementing Monitors
Using one semaphore for each waiting thread --- making sure it indeed gets
the message when it is signalled.
class Condition { List waitQueue; }
Condition::Wait(Lock* lock) {
Semaphore *w;
w = new Semaphore (0);
add w to the waitQueue;
lock->Release();
w->P();
lock->Acquire();
delete w;
Condition::Signal(Lock* lock) {
Semaphore *w;
if anyone on waitQueue {
Take a waiting element off
and name it w;
w->V();
Approach
n Operating system level approach
n Create kernel threads
n Communicate priorities to kernel
n Use kernel’s communication and synchronization primitives
n But kernel threads are too expensive to create
n Solution: keep a pool of kernel threads, reuse within application
n Problems:
n Context switches are still slow
n Kernel keeps lot more state around and is not aware of user-level
program properties
n Cannot customize the scheduling policy
Approach
n One kernel thread for each processor in the system
n Implement user-level threads entirely at the user-level in
the runtime system
n Any user thread can run on any kernel thread
n Very fast thread creation and context switch
n Fast synchronization
n Can support much finer-grained parallelism
n Problem: Two schedulers!
n What if a task does blocking I/O
n Loses CPU
n What if there are other applications running on the machine?
n Application might lose a kernel thread at a “bad time”
n Application might sometimes need fewer threads
Scheduler Activations
n Mechanism of communicating between the two schedulers
n Scheduler activation:
n Vessel for running user threads (acts like a kernel thread)
n Can think of it as a virtual processor
n Notifies the user-level runtime system of interesting kernel events
n Provides space for saving processor context of the currently running
user thread when the thread is stopped in the kernel
n Old world: fixed # of kernel threads
n New world: fixed number of “running” threads
Scenario 1
n Application has certain number of activations running
n If an activation is blocked:
n New activation is created
n Allows the user-level scheduler to run in this new activation
n Runtime scheduler can schedule another user thread to run on the
new activation
X X
Scenario 4
n New CPU becomes available
n Kernel creates a new activation
n User-level scheduler picks a new thread to execute on the
new activation
n In addition, at any point the application can tell kernel it
doesn’t need extra CPUs
Summary
n Three key features about this approach:
n Goal is the get user-level threads performance with the scheduling
consistency provided by kernel-level threads in multiprocessors
n The problem to solve: coordinating two independent thread
schedulers
n Scheduler activations used to transmit information between the two
as well as to provide virtual processors
n Lesson: export your functionality (in this case, threads) out
of the kernel for improved performance and flexibility
n Figure out how to interact with the kernel “just enough”