005 Readerwriter
005 Readerwriter
Lecture 5
Note: Some slides and/or pictures in the following are adapted from slides
©2005 Silberschatz, Galvin, and Gagne. Slides courtesy of Anthony D.
Joseph, John Kubiatowicz, AJ Shankar, George Necula, Alex Aiken, Eric
Brewer, Ras Bodik, Ion Stoica, Doug Tygar, and David Wagner.
Goals for Today
• Atomic instruction sequence
Acquire() { Release() {
// Short busy-wait time // Short busy-wait time
while (test&set(guard)); while (test&set(guard));
if (value == BUSY) { if anyone on wait queue {
put thread on wait queue; take thread off wait queue
Place on ready queue;
go to sleep() & guard = 0; } else {
} else { value = FREE;
value = BUSY; }
guard = 0; guard = 0;
}
}
• Note: sleep has to be sure to reset the guard variable
– Why can’t we do it just before or just after the sleep?
Locks using test&set vs. Interrupts
• Compare to “disable interrupt” solution (last lecture)
Acquire() { Release() {
disable interrupts; disable interrupts;
if (value == BUSY) { if (anyone on wait queue) {
put thread on wait queue; take thread off wait queue
Go to sleep(); Place on ready queue;
// Enable interrupts? } else {
} else { value = FREE;
}
value = BUSY;
enable interrupts;
} }
enable interrupts;
}
• Basically replace
– disable interrupts while (test&set(guard));
– enable interrupts guard = 0;
Locks using test&set vs. Interrupts
• Compare to “disable interrupt” solution (last lecture)
Acquire() { Release() {
while (test&set(guard)); while (test&set(guard));
if (value == BUSY) { if (anyone on wait queue) {
put thread on wait queue; take thread off wait queue
Go to sleep(); Place on ready queue;
// guard = 0; } else {
} else { value = FREE;
}
value = BUSY;
guard = 0;
} }
guard = 0;
}
• Basically replace
– disable interrupts while (test&set(guard));
– enable interrupts guard = 0;
Recap: Locks
int value = 0;
Acquire() {
// Short busy-wait time
Acquire() { disable interrupts;
disable interrupts; if (value == 1) {
} put thread on wait-queue;
go to sleep() //??
lock.Acquire(); } else {
… value = 1;
enable interrupts;
critical section;
}
… }
lock.Release();
Release() { Release() {
enable interrupts; // Short busy-wait time
} disable interrupts;
if anyone on wait queue {
take thread off wait-queue
If one thread in critical Place on ready queue;
section, no other } else {
value = 0;
activity (including OS) }
can run! enable interrupts;
}
Recap: Locks
int guard = 0;
int value = 0;
Acquire() {
// Short busy-wait time
int value = 0; while(test&set(guard));
Acquire() { if (value == 1) {
while(test&set(value)); put thread on wait-queue;
} go to sleep()& guard = 0;
lock.Acquire(); } else {
… value = 1;
guard = 0;
critical section;
}
… }
lock.Release();
Release() { Release() {
value = 0; // Short busy-wait time
} while (test&set(guard));
if anyone on wait queue {
take thread off wait-queue
Place on ready queue;
Threads waiting to } else {
enter critical section value = 0;
}
busy-wait guard = 0;
}
Where are we going with
synchronization?
Programs Shared Programs
Higher-
level Locks Semaphores Monitors Send/Receive
API
Value=2
Value=0
Value=1
Two Uses of Semaphores
• Mutual Exclusion (initial value = 1)
– Also called “Binary Semaphore”.
– Can be used for mutual exclusion:
semaphore.P();
// Critical section goes here
semaphore.V();
• Scheduling Constraints (initial value = 0)
– Allow thread 1 to wait for a signal from thread 2, i.e., thread 2
schedules thread 1 when a given constrained is satisfied
– Example: suppose you had to implement ThreadJoin which
must wait for thread to terminiate:
Initial value of semaphore = 0
ThreadJoin {
semaphore.P();
}
ThreadFinish {
semaphore.V();
}
Producer-consumer with a bounded buffer
Producer Buffer Consumer
• Problem Definition
– Producer puts things into a shared buffer
– Consumer takes them out
– Need synchronization to coordinate producer/consumer
• Don’t want producer and consumer to have to work in
lockstep, so put a fixed-size buffer between them
– Need to synchronize access to this buffer
– Producer needs to wait if buffer is full
– Consumer needs to wait if buffer is empty
Producer(item) {
emptySlots.P(); // Wait until space
mutex.P(); // Wait until machine free
Enqueue(item);
mutex.V();
fullSlots.V(); // Tell consumers there is
// more coke
}
Consumer() {
fullSlots.P(); // Check if there’s a coke
mutex.P(); // Wait until machine free
item = Dequeue();
mutex.V();
emptySlots.V(); // tell producer need more
return item;
}
Discussion about Solution
Decrease # of Increase # of
• Why asymmetry? empty slots occupied slots
– Producer does: emptySlots.P(), fullSlots.V()
– Consumer does: fullSlots.P(), emptySlots.V()
Decrease # of Increase # of
occupied slots empty slots
Motivation for Monitors and Condition
Variables
• Semaphores are a huge step up; just think of trying to do
the bounded buffer with only loads and stores
AddToQueue(item) {
lock.Acquire(); // Lock shared data
queue.enqueue(item); // Add item
lock.Release(); // Release Lock
}
RemoveFromQueue() {
lock.Acquire(); // Lock shared data
item = queue.dequeue();// Get next item or null
lock.Release(); // Release Lock
return(item); // Might return null
}
• Not very interesting use of “Monitor”
– It only uses a lock with no condition variables
– Cannot put consumer to sleep if no work!
Condition Variables
• Operations:
– Wait(&lock): Atomically release lock and go to sleep. Re-
acquire lock later, before returning.
– Signal(): Wake up one waiter, if any
– Broadcast(): Wake up all waiters
AddToQueue(item) {
lock.Acquire(); // Get Lock
queue.enqueue(item); // Add item
dataready.signal(); // Signal any waiters
lock.Release(); // Release Lock
}
RemoveFromQueue() {
lock.Acquire(); // Get Lock
while (queue.isEmpty()) {
dataready.wait(&lock); // If nothing, sleep
}
item = queue.dequeue(); // Get next item
lock.Release(); // Release Lock
return(item);
}
Mesa vs. Hoare monitors
… Lock.Acquire()
lock.Acquire() …
… Lock, CPU if (queue.isEmpty()) {
dataready.signal(); dataready.wait(&lock);
Loc
… k,
CPU
lock.Release(); }
…
lock.Release();
Mesa monitors
• Signaler keeps lock and processor
• Waiter placed on a local “e” queue for the monitor
• Practically, need to check condition again after wait
• Most real operating systems
schedule() {
if there is a thread in e
select and remove one thread from e and restart it
else
lock.Release()
Mesa monitors – lock transfer
Q: How do the scheduled threads get a lock on the monitor
when they restart?
A: At every exit from the monitor, and the end of every wait
call where there would normally be a Release, there is a
…
call to “schedule”: which does a Release or transfer.
lock.Acquire()
wait() {
…
add this thread to this.queue
dataready.signal();
schedule();
…
sleep();
schedule();
}
schedule() {
if there is a thread in e
select and remove one thread from e and restart it
else
lock.Release()
Mesa monitors – lock transfer
Q: How do the scheduled threads get a lock on the monitor
when they restart?
A: At every exit from the monitor, and the end of every wait
call where there would normally be a Release, there is a
…
call to “schedule”: which does a Release or transfer.
lock.Acquire()
wait() {
…
add this thread to this.queue
dataready.signal();
schedule();
…
sleep();
schedule();
}
schedule() {
if there is a thread in e XFER
select and remove one thread from e and restart it
else
lock.Release()
Mesa monitors – lock transfer
Q: How do the scheduled threads get a lock on the monitor
when they restart?
A: At every exit from the monitor, and the end of every wait
call where there would normally be a Release, there is a
…
call to “schedule”: which does a Release or transfer.
lock.Acquire()
wait() {
…
add this thread to this.qeue
dataready.signal();
schedule();
…
sleep();
schedule();
}
schedule() {
if there is a thread in e
select and remove one thread from e and restart it
else
lock.Release() Release
Summary
• Locks construction based on atomic seq. of instructions
– Must be very careful not to waste/tie up machine resources
» Shouldn’t spin wait for long
– Key idea: Separate lock variable, use hardware mechanisms to
protect modifications of that variable
• Semaphores
– Generalized locks
– Two operations: P(), V()
• Monitors: A synchronous object plus one or more condition
variables
– Always acquire lock before accessing shared data
– Use condition variables to wait inside critical section
» Three Operations: Wait(), Signal(), and Broadcast()