0% found this document useful (0 votes)
162 views

Semaphores and Monitors

This document discusses semaphores and monitors as synchronization primitives for controlling access to shared resources among concurrent threads or processes. It begins by explaining semaphores, including their basic operations of P and V. It then covers different types of semaphores like binary and counting semaphores. Examples are provided for how semaphores can solve classic synchronization problems like the bounded buffer problem and readers/writers problem. The document concludes by introducing monitors as an alternative approach that provides synchronization through associated condition variables and procedures.

Uploaded by

rinspd
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
162 views

Semaphores and Monitors

This document discusses semaphores and monitors as synchronization primitives for controlling access to shared resources among concurrent threads or processes. It begins by explaining semaphores, including their basic operations of P and V. It then covers different types of semaphores like binary and counting semaphores. Examples are provided for how semaphores can solve classic synchronization problems like the bounded buffer problem and readers/writers problem. The document concludes by introducing monitors as an alternative approach that provides synchronization through associated condition variables and procedures.

Uploaded by

rinspd
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPT, PDF, TXT or read online on Scribd
You are on page 1/ 30

CSE 451:

Operating
Systems
Winter 2012
Semaphores and
Monitors
Mark
Zbikowski
Gary Kimura

Semaphores
Semaphore = a synchronization primitive
higher level of abstraction than locks
invented by Dijkstra in 1968, as part of the THE operating
system

A semaphore is:
a variable that is manipulated through two operations,
P and V (Dutch for test and increment)
P(sem) (wait/down)
block until sem > 0, then subtract 1 from sem and proceed

V(sem) (signal/up)
add 1 to sem

Do these operations atomically

11/10/15

Blocking in semaphores
Each semaphore has an associated queue of threads
when P(sem) is called by a thread,
if sem was available (>0), decrement sem and let thread
continue
if sem was unavailable (<=0), place thread on associated
queue; dispatch some other runnable thread

when V(sem) is called by a thread


if thread(s) are waiting on the associated queue, unblock one
place it on the ready queue
might as well let the V-ing thread continue execution
or not, depending on priority

otherwise (when no threads are waiting on the sem),


increment sem
the signal is remembered for next time P(sem) is called

Semaphores thus have history


11/10/15

Abstract implementation
P/wait/down(sem)
acquire real mutual exclusion
if sem is available (>0), decrement sem; release real mutual
exclusion; let thread continue
otherwise, place thread on associated queue; release real
mutual exclusion; run some other thread

V/signal/up(sem)
acquire real mutual exclusion
if thread(s) are waiting on the associated queue, unblock one
(place it on the ready queue)
if no threads are on the queue, sem is incremented
the signal is remembered for next time P(sem) is called

release real mutual exclusion


[the V-ing thread continues execution or is preempted]

11/10/15

Two types of semaphores


Binary semaphore (aka mutex semaphore)
sem is initialized to 1
guarantees mutually exclusive access to resource (e.g., a
critical section of code)
only one thread/process allowed entry at a time

Counting semaphore
sem is initialized to N
N = number of units available

represents resources with many (identical) units available


allows threads to enter as long as more units are available

11/10/15

Usage

From the programmers perspective, P and V on a binary


semaphore are just like Acquire and Release on a lock
P(sem)
.
.
.
do whatever stuff requires mutual exclusion; could conceivably
be a lot of code
.
.
.
V(sem)

same lack of programming language support for correct usage

Important differences in the underlying implementation, however

11/10/15

Pressing questions

How do you acquire real mutual exclusion?

Why is this any better than using a spinlock (test-and-set) or


disabling interrupts (assuming youre in the kernel) in lieu of a
semaphore?

What if some bozo issues an extra V?

What if some bozo forgets to P?

11/10/15

Example: Bounded buffer problem


AKA producer/consumer problem
there is a buffer in memory with N entries
producer threads insert entries into it (one at a time)
consumer threads remove entries from it (one at a time)

Threads are concurrent


so, we must use synchronization constructs to control
access to shared variables describing buffer state

tail

11/10/15

head

Bounded buffer using semaphores


(both binary and counting)
var mutex: semaphore = 1
empty: semaphore = n
full: semaphore = 0

;mutual exclusion to shared data


;count of empty buffers (all empty to start)
;count of full buffers (none full to start)

producer:
P(empty)
; one fewer buffer, block if none available
P(mutex)
; get access to pointers
<add item to buffer>
V(mutex)
; done with pointers
V(full)
; note one more full buffer

consumer:
P(full)
;wait until theres a full buffer
P(mutex)
;get access to pointers
<remove item from buffer>
V(mutex)
; done with pointers
V(empty)
; note theres an empty buffer
<use the item>

11/10/15

Note 1:
I have elided all the code
concerning which is the
first full buffer, which is the
last full buffer, etc.
Note 2:
Try to figure out how to do
this without using counting
semaphores!

Example: Readers/Writers
Description:
A single object is shared among several threads/processes
Sometimes a thread just reads the object
Sometimes a thread updates (writes) the object
We can allow multiple readers at a time
why?

We can only allow one writer at a time


why?

11/10/15

10

Readers/Writers using semaphores


var mutex: semaphore = 1
wrt: semaphore = 1
readcount: integer = 0

; controls access to readcount


; control entry for a writer or first reader
; number of active readers

writer:
P(wrt)
V(wrt)

; any writers or readers?


<perform write operation>
; allow others

reader:
P(mutex)
; ensure exclusion
readcount++
; one more reader
if readcount == 1 then P(wrt)
; if were the first, synch with writers
V(mutex)
<perform read operation>
P(mutex)
; ensure exclusion
readcount-; one fewer reader
if readcount == 0 then V(wrt)
; no more readers, allow a writer
V(mutex)
11/10/15

11

Readers/Writers notes
Notes:
the first reader blocks on P(wrt) if there is a writer
any other readers will then block on P(mutex)

if a waiting writer exists, the last reader to exit signals the


waiting writer
can new readers get in while a writer is waiting?

when writer exits, if there is both a reader and writer waiting,


which one goes next?

11/10/15

12

Semaphores vs. Locks

Threads that are blocked at the level of program logic are


placed on queues, rather than busy-waiting

Busy-waiting may be used for the real mutual exclusion


required to implement P and V
but these are very short critical sections totally independent of
program logic

In the not-very-interesting case of a thread package


implemented in an address space powered by only a single
kernel thread, its even easier that this

11/10/15

13

Problems with semaphores (and locks)


They can be used to solve any of the traditional
synchronization problems, but:
semaphores are essentially shared global variables
can be accessed from anywhere (bad software engineering)

there is no connection between the semaphore and the data


being controlled by it
used for both critical sections (mutual exclusion) and for
coordination (scheduling)
no control over their use, no guarantee of proper usage

Thus, they are prone to bugs


another (better?) approach: use programming language
support
11/10/15

14

One More Approach: Monitors

A monitor is a programming language construct that supports


controlled access to shared data
synchronization code is added by the compiler
why does this help?

A monitor encapsulates:
shared data structures
procedures that operate on the shared data
synchronization between concurrent threads that invoke those
procedures

Data can only be accessed from within the monitor, using the
provided procedures
protects the data from unstructured access

Addresses the key usability issues that arise with semaphores

11/10/15

15

A monitor

shared data

waiting queue of threads


trying to enter the monitor

at most one thread


in monitor at a
time
11/10/15

operations (methods)

16

Monitor facilities
Automatic mutual exclusion
only one thread can be executing inside at any time
thus, synchronization is implicitly associated with the monitor it
comes for free

if a second thread tries to execute a monitor procedure, it


blocks until the first has left the monitor
more restrictive than semaphores
but easier to use (most of the time)

But, theres a problem

11/10/15

17

Example: Bounded Buffer Scenario

Produce()
Consume()

Buffer is empty
Now what?
11/10/15

18

Example: Bounded Buffer Scenario

Produce()
Consume()

Buffer is empty
Now what?
11/10/15

19

Condition variables
A place to wait; sometimes called a rendezvous point
Required for monitors
So useful theyre often provided even when monitors arent
available

Three operations on condition variables


wait(c)
release monitor lock, so somebody else can get in
wait for somebody else to signal condition
thus, condition variables have associated wait queues

signal(c)
wake up at most one waiting thread
if no waiting threads, signal is lost
this is different than semaphores: no history!

broadcast(c)
wake up all waiting threads
11/10/15

20

Bounded buffer using (Hoare) monitors


Monitor bounded_buffer {
buffer resources[N];
condition not_full, not_empty;
produce(resource x) {
if (array resources is full, determined maybe by a count)
wait(not_full);
insert x in array resources
signal(not_empty);
}
consume(resource *x) {
if (array resources is empty, determined maybe by a count)
wait(not_empty);
*x = get resource from array resources
signal(not_full);
}

11/10/15

21

Runtime system calls for (Hoare)


monitors

EnterMonitor(m) {guarantee mutual exclusion}


ExitMonitor(m) {hit the road, letting someone else run}
Wait(c) {step out until condition satisfied}
Signal(c) {if someones waiting, step out and let him run}

11/10/15

22

Bounded buffer using (Hoare) monitors


Monitor bounded_buffer {
buffer resources[N];
condition not_full, not_empty;
EnterMonitor
procedure add_entry(resource x) {
if (array resources is full, determined maybe by a count)
wait(not_full);
insert x in array resources
ExitMonitor
signal(not_empty);
}
EnterMonitor
procedure get_entry(resource *x) {
if (array resources is empty, determined maybe by a count)
wait(not_empty);
*x = get resource from array resources
ExitMonitor
signal(not_full);
}
11/10/15

23

There is a subtle issue with that code


Who runs when the signal() is done and there is a thread waiting
on the condition variable?
Hoare monitors: signal(c) means
run waiter immediately
signaller blocks immediately
condition guaranteed to hold when waiter runs
but, signaller must restore monitor invariants before signalling!
cannot leave a mess for the waiter, who will run immediately!

Mesa monitors: signal(c) means


waiter is made ready, but the signaller continues
waiter runs when signaller leaves monitor (or waits)

signaller need not restore invariant until it leaves the monitor


being woken up is only a hint that something has changed
signalled condition may no longer hold
must recheck conditional case
11/10/15

24

Hoare vs. Mesa Monitors


Hoare monitors:

if (notReady) wait(c)

Mesa monitors:

while (notReady) wait(c)

Mesa monitors easier to use


more efficient: fewer context switches
directly supports broadcast

Hoare monitors leave less to chance


when wake up, condition guaranteed to be what you expect

11/10/15

25

Runtime system calls for Hoare monitors


EnterMonitor(m) {guarantee mutual exclusion}
if m occupied, insert caller into queue m
else mark as occupied, insert caller into ready queue
choose somebody to run

ExitMonitor(m) {hit the road, letting someone else run}

11/10/15

if queue m is empty, then mark m as unoccupied


else move a thread from queue m to the ready queue
insert caller in ready queue
choose someone to run

26

Runtime system calls for Hoare monitors


(contd)
Wait(c) {step out until condition satisfied}

if queue m is empty, then mark m as unoccupied


else move a thread from queue m to the ready queue
put the caller on queue c
choose someone to run

Signal(c) {if someones waiting, step out and let him run}
if queue c is empty then put the caller on the ready queue
else move a thread from queue c to the ready queue, and put the
caller into queue m
choose someone to run

11/10/15

27

Runtime system calls for Mesa monitors


EnterMonitor(m) {guarantee mutual exclusion}

ExitMonitor(m) {hit the road, letting someone else run}


Wait(c) {step out until condition satisfied}


Signal(c) {if someones waiting, give him a shot after Im


done}
if queue c is occupied, move one thread from queue c to queue m
return to caller

11/10/15

28

Broadcast(c) {food fight!}


move all threads on queue c onto queue m
return to caller

11/10/15

29

Monitor Summary
Language supports monitors
Compiler understands them
compiler inserts calls to runtime routines for

monitor entry
monitor exit
signal
Wait

Language/object encapsulation ensures correctness


Sometimes! With conditions you STILL need to think about
synchronization

Runtime system implements these routines


moves threads on and off queues
ensures mutual exclusion!
11/10/15

30

You might also like