You are on page 1of 20

CENG334

Introduction to Operating Systems

Monitors, Condition variabless


Topics:
•Monitors

•Condition Variables

Erol Sahin

Dept of Computer Eng.


Middle East Technical University
Ankara, TURKEY

URL: http://kovan.ceng.metu.edu.tr/ceng334
Week 2 13/03/07 1

Semaphores
Higher-level synchronization
construct Semaphore

 Designed by Edsger Dijkstra in the


1960's, part of the THE operating
system (classic stuff!)

Semaphore is a shared counter

Two operations on semaphores:

P() or wait() or down()


 From Dutch “proeberen”, meaning “test”
 Atomic action:
 Wait for semaphore value to become > 0, then decrement it

V() or signal() or up()


 From Dutch “verhogen”, meaning “increment”
 Atomic action:
 Increments semaphore value by 1.

Adapted from Matt Welsh’s (Harvard University) slides. 2

1
Producer/Consumer: naïve implementation

Producer Consumer

int count = 0;

Producer() { Consumer() {
int item; int item;
while (TRUE) { while (TRUE) {
item = bake(); if (count == 0) sleep();
if (count == N) sleep(); item = remove_item();
insert_item(item); count = count – 1;
count = count + 1; if (count == N-1)
if (count == 1) wakeup(producer);
wakeup(consumer); eat(item);
} }
} }

What's wrong with this code? What if we context switch right here??

Adapted from Matt Welsh’s (Harvard University) slides. 3

A fix using semaphores

Producer Consumer

Semaphore mutex = 1;
Semaphore empty = N;
Semaphore full = 0;
Consumer() {
Producer() {
int item;
int item;
while (TRUE) {
while (TRUE) {
down(full);
item = bake();
down(mutex);
down(empty);
item = remove_item();
down(mutex);
up(mutex);
insert_item(item);
up(empty);
up(mutex);
eat(item);
up(full);
}
}
}
}

Adapted from Matt Welsh’s (Harvard University) slides. 4

2
Readers/Writers Problem
W

R
R
R

Motivation: Consider a shared database


 Two classes of users:
 Readers – never modify database

 Writers – read and modify database

 Is using a single lock on the whole database sufficient?


 Like to have many readers at the same time

 Only one writer at a time

Reader/Writers Problem
Single shared object

Want to allow any number of threads to read simultaneously

But, only one thread should be able to write to the object at a time
 (And, not interfere with any readers...)

Conditions:
 A Reader should only wait for a Writer to complete its do_write().

 A Reader should not wait for other Readers to complete their do_read().

 The Writer should wait for the other Writers to complete their do_write().

 The Writer should wait for all the Readers to complete their do_read().

Basic structure of a solution



 Reader()
Wait until no writers
Access data base
Check out – wake up a waiting writer
 Writer()
Wait until no active readers or writers
Access database
Check out – wake up waiting readers or writer

Adapted from Matt Welsh’s (Harvard University) slides. 6

3
Readers-Writers Problem
One issue we need to settle, to clarify problem statement.
 Suppose that a writer is active and a mixture of readers and writers now shows up.
Who should get in next?
 Or suppose that a writer is waiting and an endless of stream of readers keeps
showing up. Is it fair for them to become active?

We’ll favor a kind of back-and-forth form of fairness:


 Once a reader is waiting, readers will get in next.
 If a writer is waiting, one writer will get in next.

Reader/Writers

Why the test


Semaphore mutex = 1; Reader() { here??
Semaphore wrt = 1; down(mutex);
int readcount = 0; readcount++;
if (readcount == 1) {
Writer() { down(wrt);
down(wrt); }
do_write(); up(mutex);
up(wrt); do_read();
} down(mutex);
readcount--;
if (readcount == 0) {
up(wrt);
}
up(mutex);
}

Adapted from Matt Welsh’s (Harvard University) slides. 8

4
Issues with Semaphores
Much of the power of semaphores derives from calls to
down() and up() that are unmatched
 See previous example!

Unlike locks, acquire() and release() are not always paired.

This means it is a lot easier to get into trouble with semaphores.


 “More rope”

Would be nice if we had some clean, well-defined language


support for synchronization...
 Java does!

Adapted from Matt Welsh’s (Harvard University) slides. 9

Monitors
 A high-level abstraction that provides a convenient and
effective mechanism for process synchronization
 Only one process may be active within the monitor at a time

10

5
Monitor with Condition Variables
 condition x, y;

 Two operations on a condition


variable:
 x.wait () – a process that

invokes the operation is


suspended.
 x.signal () – resumes one

of processes (if any) that


invoked x.wait ()

11

Java Synchronization Support


Java synchronization based on locks and condition variables
 Every Java object can be used as a lock
Object foo;
synchronized (foo) {
// Do some stuff with 'foo' locked...
}

What's nice about this?


 Compiler ensures that lock is released before leaving the synchronized block
 Even if there is an exception!!

Condition variable (CV): allows a thread to wait or wakeup other


threads

Three operations on condition variables:


 wait() -- Block until another thread calls notify() or notifyAll() on the CV
 notify() or signal -- Wake up one thread waiting on the CV
 notifyAll() -- Wake up all threads waiting on the CV

Adapted from Matt Welsh’s (Harvard University) slides. 12

6
Bounded Buffer using CV's
int theArray[ARRAY_SIZE], size; int get() {
Object theLock; int item;
synchronized(theLock) {
void put(int val) { while (size == 0) {
synchronized(theLock) { theLock.wait();
while (size == ARRAY_SIZE) { }
theLock.wait(); item = getItemFromArray()
} size--;
addItemToArray(val); if (size == ARRAY_SIZE-1)
size++; theLock.notify();
if (size == 1) }
theLock.notify(); return item;
}
} }

Problems with this code??

Adapted from Matt Welsh’s (Harvard University) slides. 13

Bounded Buffer using CV's


int theArray[ARRAY_SIZE], size; int get() {
Object theLock; int item;
synchronized(theLock) {
void put(int val) { while (size == 0) {
synchronized(theLock) { theLock.wait();
while (size == ARRAY_SIZE) { }
theLock.wait(); item = getItemFromArray()
} size--;
addItemToArray(val); if (size == ARRAY_SIZE-1)
size++; theLock.notify();
if (size == 1) }
theLock.notify(); return item;
}
} }

Assumes only a single thread calling put() and get() at a time!


If two threads call get(), then two threads call put(), only one
will be woken up!!

Adapted from Matt Welsh’s (Harvard University) slides. 14

7
How to fix this problem?
int theArray[ARRAY_SIZE], size; int get() {
Object theLock; int item;
synchronized(theLock) {
void put(int val) { while (size == 0) {
synchronized(theLock) { theLock.wait();
while (size == ARRAY_SIZE) { }
theLock.wait(); item = getItemFromArray()
} size--;
addItemToArray(val); if (size == ARRAY_SIZE-1)
size++; theLock.notifyAll();
if (size == 1) }
theLock.notifyAll(); return item;
}
} }

Every notifyAll() will cause all threads to wake up and re-check the
empty or full condition.
 Could be inefficient if a lot of threads are involved...

Adapted from Matt Welsh’s (Harvard University) slides. 15

Monitors
This style of using locks and CV's to protect access to a shared
object is often called a monitor
 Think of a monitor as a lock protecting an object, plus a queue of waiting threads.

Shared data

Waiting threads

At most one thread Methods accessing


in the monitor at a shared data
time

How is this different than a lock???

Adapted from Matt Welsh’s (Harvard University) slides. 16

8
Monitors
unlocked

Shared data

Methods accessing
shared data

Adapted from Matt Welsh’s (Harvard University) slides. 17

Monitors
locked

Shared data

zzzz...

Methods accessing
zzzz... shared data

Sleeping thread no longer “in” the monitor.


(But not on the waiting queue either! Why?)

Adapted from Matt Welsh’s (Harvard University) slides. 18

9
Monitors
Monitor stays locked! locked
(Lock now owned by
different thread...)
Shared data

notify()
Methods accessing
zzzz... shared data

Adapted from Matt Welsh’s (Harvard University) slides. 19

Monitors
locked

Shared data

notify()
Methods accessing
shared data

Adapted from Matt Welsh’s (Harvard University) slides. 20

10
Monitors
locked

Shared data

Methods accessing
shared data

No guarantee which order threads get into the monitor.


(Not necessarily FIFO!)

Adapted from Matt Welsh’s (Harvard University) slides. 21

CVs in Java
In Java, a thread can only call wait(), notify(), or notifyAll() if it
holds the lock of the associated object
Object foo;

/* NOT ALLOWED!!! */
foo.notify();

/* This is ok ... */
synchronized(foo) { foo.notify(); }

Why?

Adapted from Matt Welsh’s (Harvard University) slides. 22

11
CVs in Java
In Java, a thread can only call wait(), notify(), or notifyAll() if it
holds the lock of the associated object
Object foo;

/* NOT ALLOWED!!! */
foo.notify();

/* This is ok ... */
synchronized(foo) { foo.notify(); }

Why?
 Want to avoid common race conditions!

if (foo.count < 0) { /* Another thread... */


synchronized (foo) { synchronized (foo) {
foo.wait(); foo.count++;
} foo.notify();
} }

Adapted from Matt Welsh’s (Harvard University) slides. 23

Hoare vs. Mesa Monitor Semantics


The monitor notify() operation can have two different meanings:

Hoare monitors (1974)


 notify(CV) means to run the waiting thread immediately
 Causes notifying thread to block

Mesa monitors (Xerox PARC, 1980)


 notify(CV) puts waiting thread back onto the “ready queue” for the monitor
 But, notifying thread keeps running

Adapted from Matt Welsh’s (Harvard University) slides. 24

12
Hoare vs. Mesa Monitor Semantics
The monitor notify() operation can have two different meanings:

Hoare monitors (1974)


 notify(CV) means to run the waiting thread immediately
 Causes notifying thread to block

Mesa monitors (Xerox PARC, 1980)


 notify(CV) puts waiting thread back onto the “ready queue” for the monitor
 But, notifying thread keeps running

What's the practical difference?


 In Hoare-style semantics, the “condition” that triggered the notify()
will always be true when the awoken thread runs
 For example, that the buffer is now no longer empty

 In Mesa-style semantics, awoken thread has to recheck the condition


 Since another thread might have beaten it to the punch

Adapted from Matt Welsh’s (Harvard University) slides. 25

Hoare vs. Mesa monitors


Need to be careful about precise definition of signal and wait.
while (n==0) {
wait(not_empty); // If nothing, sleep
}
item = getItemFromArray(); // Get next item
 Why didn’t we do this?

if (n==0) {
wait(not_empty); // If nothing, sleep
}
removeItemFromArray(val); // Get next item

Answer: depends on the type of scheduling


 Hoare-style (most textbooks):
 Signaler gives lock, CPU to waiter; waiter runs immediately
 Waiter gives up lock, processor back to signaler when it exits critical section or if it
waits again
 Mesa-style (Java, most real operating systems):
 Signaler keeps lock and processor
 Waiter placed on ready queue with no special priority
 Practically, need to check condition again after wait

26

13
Revisit: Readers/Writers Problem
Correctness Constraints:
 Readers can access database when no writers
 Writers can access database when no readers or writers
 Only one thread manipulates state variables at a time

State variables (Protected by a lock called “lock”):


 int NReaders: Number of active readers; initially = 0
 int WaitingReaders: Number of waiting readers; initially = 0
 int NWriters: Number of active writers; initially = 0
 int WaitingWriters: Number of waiting writers; initially = 0
 Condition canRead = NIL
 Conditioin canWrite = NIL

27

Readers and Writers

Monitor ReadersNWriters {
int WaitingWriters, WaitingReaders,NReaders, NWriters;
Condition CanRead, CanWrite;

Void BeginWrite() Void BeginRead()


{ {
if(NWriters == 1 || NReaders > 0) if(NWriters == 1 || WaitingWriters > 0)
{ {
++WaitingWriters; ++WaitingReaders;
wait(CanWrite); Wait(CanRead);
--WaitingWriters; --WaitingReaders;
} }
NWriters = 1; ++NReaders;
} Signal(CanRead);
Void EndWrite() }
{
NWriters = 0; Void EndRead()
if(WaitingReaders) {
Signal(CanRead); if(--NReaders == 0)
else Signal(CanWrite);
Signal(CanWrite);
} }

28

14
Readers and Writers

Monitor ReadersNWriters {
int WaitingWriters, WaitingReaders,NReaders, NWriters;
Condition CanRead, CanWrite;

Void BeginWrite() Void BeginRead()


{ {
if(NWriters == 1 || NReaders > 0) if(NWriters == 1 || WaitingWriters > 0)
{ {
++WaitingWriters; ++WaitingReaders;
wait(CanWrite); Wait(CanRead);
--WaitingWriters; --WaitingReaders;
} }
NWriters = 1; ++NReaders;
} Signal(CanRead);
Void EndWrite() }
{
NWriters = 0; Void EndRead()
if(WaitingReaders) {
Signal(CanRead); if(--NReaders == 0)
else Signal(CanWrite);
Signal(CanWrite);
} }

29

Readers and Writers

Monitor ReadersNWriters {
int WaitingWriters, WaitingReaders,NReaders, NWriters;
Condition CanRead, CanWrite;

Void BeginWrite() Void BeginRead()


{ {
if(NWriters == 1 || NReaders > 0) if(NWriters == 1 || WaitingWriters > 0)
{ {
++WaitingWriters; ++WaitingReaders;
wait(CanWrite); Wait(CanRead);
--WaitingWriters; --WaitingReaders;
} }
NWriters = 1; ++NReaders;
} Signal(CanRead);
Void EndWrite() }
{
NWriters = 0; Void EndRead()
if(WaitingReaders) {
notify(CanRead); if(--NReaders == 0)
else notify(CanWrite);
notify(CanWrite);
} }

30

15
Readers and Writers

Monitor ReadersNWriters {
int WaitingWriters, WaitingReaders,NReaders, NWriters;
Condition CanRead, CanWrite;

Void BeginWrite() Void BeginRead()


{ {
if(NWriters == 1 || NReaders > 0) if(NWriters == 1 || WaitingWriters > 0)
{ {
++WaitingWriters; ++WaitingReaders;
wait(CanWrite); Wait(CanRead);
--WaitingWriters; --WaitingReaders;
} }
NWriters = 1; ++NReaders;
} notify(CanRead);
Void EndWrite() }
{
NWriters = 0; Void EndRead()
if(WaitingReaders) {
notify(CanRead); if(--NReaders == 0)
else notify(CanWrite);
notify(CanWrite);
} }

31

Understanding the Solution


A writer can enter if there are no other active writers and no readers
are waiting

32

16
Readers and Writers

Monitor ReadersNWriters {
int WaitingWriters, WaitingReaders,NReaders, NWriters;
Condition CanRead, CanWrite;

Void BeginWrite() Void BeginRead()


{ {
if(NWriters == 1 || NReaders > 0) if(NWriters == 1 || WaitingWriters > 0)
{ {
++WaitingWriters; ++WaitingReaders;
wait(CanWrite); Wait(CanRead);
--WaitingWriters; --WaitingReaders;
} }
NWriters = 1; ++NReaders;
} notify(CanRead);
Void EndWrite() }
{
NWriters = 0; Void EndRead()
if(WaitingReaders) {
notify(CanRead); if(--NReaders == 0)
else notify(CanWrite);
notify(CanWrite);
} }

33

Understanding the Solution


A reader can enter if
 There are no writers active or waiting

So we can have many readers active all at once

Otherwise, a reader waits (maybe many do)

34

17
Readers and Writers

Monitor ReadersNWriters {
int WaitingWriters, WaitingReaders,NReaders, NWriters;
Condition CanRead, CanWrite;

Void BeginWrite() Void BeginRead()


{ {
if(NWriters == 1 || NReaders > 0) if(NWriters == 1 || WaitingWriters > 0)
{ {
++WaitingWriters; ++WaitingReaders;
wait(CanWrite); Wait(CanRead);
--WaitingWriters; --WaitingReaders;
} }
NWriters = 1; ++NReaders;
} notify(CanRead);
Void EndWrite() }
{
NWriters = 0; Void EndRead()
if(WaitingReaders) {
notify(CanRead); if(--NReaders == 0)
else notify(CanWrite);
notify(CanWrite);
} }

35

Understanding the Solution


When a writer finishes, it checks to see if any readers are waiting
 If so, it lets one of them enter
 That one will let the next one enter, etc…

Similarly, when a reader finishes, if it was the last reader, it lets a writer
in (if any is there)

36

18
Readers and Writers

Monitor ReadersNWriters {
int WaitingWriters, WaitingReaders,NReaders, NWriters;
Condition CanRead, CanWrite;

Void BeginWrite() Void BeginRead()


{ {
if(NWriters == 1 || NReaders > 0) if(NWriters == 1 || WaitingWriters > 0)
{ {
++WaitingWriters; ++WaitingReaders;
wait(CanWrite); Wait(CanRead);
--WaitingWriters; --WaitingReaders;
} }
NWriters = 1; ++NReaders;
} notify(CanRead);
Void EndWrite() }
{
NWriters = 0; Void EndRead()
if(WaitingReaders) {
notify(CanRead); if(--NReaders == 0)
else notify(CanWrite);
notify(CanWrite);
} }

37

Understanding the Solution


It wants to be fair
 If a writer is waiting, readers queue up
 If a reader (or another writer) is active or waiting, writers queue up

 … this is mostly fair, although once it lets a reader in, it lets ALL waiting readers in all at
once, even if some showed up “after” other waiting writers

38

19
The Big Picture
The point here is that getting synchronization right is hard

How to pick between locks, semaphores, condvars, monitors???

Locks are very simple for many cases.


 Issues: Maybe not the most efficient solution
 For example, can't allow multiple readers but one writer inside a standard lock.

Condition variables allow threads to sleep while holding a lock


 Just be sure you understand whether they use Mesa or Hoare semantics!

Semaphores provide pretty general functionality


 But also make it really easy to botch things up.

Java captures a lot of useful common operations with its use of


monitors (compiler checking is nice too)
 But, not possible to implement everything directly with Java's primitives.

Adapted from Matt Welsh’s (Harvard University) slides. 39

20

You might also like