SE350: Operating Systems Lecture 7: Synchronization API Outline - - PowerPoint PPT Presentation

se350 operating systems
SMART_READER_LITE
LIVE PREVIEW

SE350: Operating Systems Lecture 7: Synchronization API Outline - - PowerPoint PPT Presentation

SE350: Operating Systems Lecture 7: Synchronization API Outline Semaphores Monitors Mesa vs. Hoare Communicating sequential process Use cases Bounded buffer Reader writer lock Recap: Locks Using Interrupts int value =


slide-1
SLIDE 1

SE350: Operating Systems

Lecture 7: Synchronization API

slide-2
SLIDE 2

Outline

  • Semaphores
  • Monitors
  • Mesa vs. Hoare
  • Communicating sequential process
  • Use cases
  • Bounded buffer
  • Reader writer lock
slide-3
SLIDE 3

Recap: Locks Using Interrupts

lock.Acquire(); … critical section; … lock.Release(); Acquire() { disable interrupts; } Release() { enable interrupts; }

If If one e threa read is s in critical sec section, no no othe her activity (i (including OS) ) can run!

int value = 0; Acquire() { // Short busy-wait time disable interrupts; if (value == 1) { put thread on wait-queue; go_to_sleep(); //?? } else { value = 1; } enable interrupts; } Release() { // Short busy-wait time disable interrupts; if (threads on wait queue) { take thread off wait-queue place it on ready queue; } else { value = 0; } enable interrupts; }

slide-4
SLIDE 4

Recap: Locks Using test&set

lock.Acquire(); … critical section; … lock.Release(); int value = 0 Acquire() { while (test&set(value)); } Release() { value = 0; }

Thr Thread ads wai aiti ting ng to to ente nter cr crit itica ical s l sect ction ion b busy-wa wait

int guard = 0; int value = 0; Acquire() { // Short busy-wait time while (test&set(guard)); if (value == 1) { put thread on wait-queue; go_to_sleep() & guard = 0; } else { value = 1; guard = 0; } } Release() { // Short busy-wait time while (test&set(guard)); if (threads on wait queue) { take thread off wait-queue place it on ready queue; } else { value = 0; } guard = 0; }

slide-5
SLIDE 5

Rules for Using Locks

  • Lock should be initially free
  • Always acquire before accessing shared data
  • Best place for acquiring lock: beginning of procedure!
  • Always release lock after finishing with shared data
  • Best place for releasing lock: end of procedure!
  • Only the lock holder can release
  • DO NOT throw lock for someone else to release
  • Never access shared data without lock
  • Danger! Don’t do it even if it’s tempting!
slide-6
SLIDE 6

Acquire Lock Before Accessing Shared Data, ALWAYS!

getP() { if (p == NULL) { lock.acquire(); if (p == NULL) { tmp = malloc(sizeof(…)); tmp->field1 = …; tmp->field2 = …; p = tmp; } lock.release(); } return p; }

  • Does this work?
  • No! Compiler/HW could make p

point to tmp before its fields are set

  • This is called double-checked locking
  • Safe but expensive solution is

getP() { lock.acquire(); if (p == NULL) { tmp = malloc(sizeof(…)); tmp->field1 = …; tmp->field2 = …; p = tmp; } lock.release(); return p; }

slide-7
SLIDE 7

Producer-Consumer with Bounded Buffer

  • Problem definition
  • Producer puts things into shared buffer
  • Consumer takes them out
  • Need to synchronize access to this buffer
  • Producer needs to wait if buffer is full
  • Consumer needs to wait if buffer is empty
  • Example 1: GCC compiler
  • cpp | cc1 | cc2 | as | ld
  • Example 2: newspaper vending machine
  • Producer can put limited number of newspapers in machine
  • Consumer can’t take newspaper out if machine is empty

Producer Consumer Buffer

slide-8
SLIDE 8

Bounded Buffer Correctness Constraints

  • Consumer must wait for producer if buffer is empty (scheduling constraint)
  • Producer must wait for consumer if buffer is full (scheduling constraint)
  • Only one thread can manipulate buffer at any time (mutual exclusion)
  • Remember why we need mutual exclusion
  • Because computers are stupid
  • Imagine delivery person filling the vending machine, and somebody comes and

tries to stick their money into the machine

slide-9
SLIDE 9

Bounded Buffer Implementation with Locks

  • If try_to_consume() returns NULL, do we know buffer is empty?
  • No! Producer might have filled buffer
  • We only know buffer was empty when we tried
  • After releasing lock our knowledge of buffer might not be accurate – we only know

state of buffer while holding the lock!

  • Is it a good idea to do while(try_to_consume() != NULL)?
  • This will delay producer’s thread from putting items on buffer – bad for everyone!

try_to_produce(item) { lock.acquire(); success = FALSE; if (queue.size() < MAX) { queue.enqueue(item); success = TRUE; } lock.release(); return success; } try_to_consume() { lock.acquire(); item = NULL; if (!queue.isEmpty()) item = queue.dequeue(); lock.release(); return item; }

slide-10
SLIDE 10

Load/Store Disable Interrupts Test&Set Compare&Swap Locks Se Semaphores Monitors Send/Receive Shared Programs Hardware Higher-level API Programs

Where Are We Going with Synchronization?

  • We will see how we can implement various higher-level

synchronization primitives using atomic operations

  • Everything is quite painful if load/store are the only atomic primitives
  • Hardware needs to provide more primitives useful at user-level
slide-11
SLIDE 11

Semaphores

  • Semaphores are a kind of generalized lock
  • First defined by Dijkstra in late 60s
  • Main synchronization primitive used in original UNIX
  • Semaphore has non-negative integer value and 2 operations
  • P(): atomic operation that waits for semaphore to become positive,

then decrements it by one

  • V(): atomic operation that increments semaphore by one, waking up

a waiting P(), if any

  • In Dutch, P stands for proberen (to test) and V stands for verhogen (to

increment)

slide-12
SLIDE 12

Semaphores (cont.)

  • Semaphores are like integers, except
  • No negative values
  • Only available operations are P & V

(cannot read/write value, except to set it initially)

  • Operations must be atomic
  • Two P’s together can’t decrement value below zero
  • Thread going to sleep in P won’t miss wakeup from V

(even if they both happen at same time)

  • Example: semaphore from railway analogy (initialized to 2)

Value=2 Value=1 Value=0 Value=1 Value=0

slide-13
SLIDE 13

Example Uses of Semaphores

  • Mutual exclusion with binary semaphore (initialized to 1)

semaphore.P(); // Critical section goes here semaphore.V();

  • Scheduling constraints (initialized to 0)

thread_join(…) { semaphore.P(); } thread_exit() { semaphore.V(); }

slide-14
SLIDE 14

Recall: Bounded Buffer Correctness Constraints

  • Consumer must wait for producer if buffer is empty (scheduling constraint)
  • Producer must wait for consumer if buffer is full (scheduling constraint)
  • Only one thread can manipulate buffer at any time (mutual exclusion)
  • Remember why we need mutual exclusion
  • Because computers are stupid
  • Imagine delivery person filling the vending machine, and somebody comes and

tries to stick their money into the machine

slide-15
SLIDE 15

Bounded Buffer Implementation with Semaphore

Semaphore emptySlots = MAX; // producer’s constraint Semaphore fullSlots = 0; // consumer’s constraint Semaphore mutex = 1; // mutual exclusion produce(item) { emptySlots.P(); // wait until there is space mutex.P(); // wait until machine is free queue.enqueue(item); mutex.V(); fullSlots.V(); // tell consumer there is } // an additional item consume() { fullSlots.P(); // wait until there is an item mutex.P(); // wait until machine is free item = queue.dequeue(); mutex.V(); emptySlots.V(); // tell producer there is an return item; // additional empty space }

General rule of thumb is to use separate semaphore for each constraint

slide-16
SLIDE 16

Discussion about Solution

  • Why asymmetry?
  • Producer does: emptySlots.P(), fullSlots.V()
  • Consumer does: fullSlots.P(), emptySlots.V()

Decrease # of empty slots Increase # of

  • ccupied slots

Increase # of empty slots Decrease # of

  • ccupied slots
slide-17
SLIDE 17

Discussion about Solution (cont.)

  • Is order of P’s important?
  • Yes! Can cause deadlock
  • Is order of V’s important?
  • No, it only might affect

scheduling efficiency

produce(item) { mutex.P(); emptySlots.P(); queue.enqueue(item); mutex.V(); fullSlots.V(); } consume() { fullSlots.P(); mutex.P(); item = queue.dequeue(); mutex.V(); emptySlots.V(); return item; }

slide-18
SLIDE 18

Semaphores Considered Harmful

“During system conception it transpired that we used the semaphores in two completely different ways. The difference is so marked that, looking back, one wonders whether it was really fair to present the two ways as uses of the very same primitives. On the one hand, we have the semaphores used for mutual exclusion, on the

  • ther hand, the private semaphores.”

Dijkstra “The structure of the ’THE’-Multiprogramming System” Communications of the ACM v. 11 n. 5 May 1968.)”

slide-19
SLIDE 19

Motivation for Monitors and Condition Variables

  • Problem: semaphores are dual purpose:
  • They are used for both mutex and scheduling constraints
  • Example: the fact that flipping of P’s in bounded buffer gives deadlock

is not immediately obvious

  • Solution: use locks for mutual exclusion and Condition

Variables (CV) for scheduling constraints

  • Definition: monitor is one lock with zero or more condition

variables for managing concurrent access to shared data

  • Some languages like Java provide this natively
  • Most others use actual locks and condition variables
slide-20
SLIDE 20

Monitor with Condition Variables

  • Lock provides mutual exclusion to shared data
  • Always acquire before accessing shared data structure
  • Always release after finishing with shared data
  • Lock initially free
  • CV is queue of threads waiting for event inside critical section
  • Key idea: make it possible to go to sleep inside critical section by

atomically releasing lock at time we go to sleep

  • Contrast to semaphores: can’t wait inside critical section
slide-21
SLIDE 21

Condition Variables Operations

  • wait(Lock *lock)
  • Atomically release lock and relinquish processor
  • Reacquire the lock when wakened
  • signal()
  • Wake up a waiter, if any
  • broadcast()
  • Wake up all waiters, if any
slide-22
SLIDE 22

Properties of Condition Variables

  • Condition variables are memoryless
  • No internal memory except a queue of waiting threads
  • No effect in calling signal/broadcast on empty queue
  • ALWAYS hold lock when calling wait(), signal(), broadcast()
  • In Birrell paper, he says you can call signal() outside of lock – IGNORE HIM

(this is only an optimization)

  • Calling wait() atomically adds thread to wait queue and releases lock
  • Re-enabled waiting threads may not run immediately
  • No atomicity between signal/broadcast and the return from wait
slide-23
SLIDE 23

Condition Variable Design Pattern

method_that_waits() { lock.acquire(); // Read/write shared state while (!testSharedState()) cv.wait(&lock); // Read/write shared state lock.release(); } method_that_signals() { lock.acquire(); // Read/write shared state // If testSharedState is now true cv.signal(); // Read/write shared state lock.release(); }

slide-24
SLIDE 24

Bounded Buffer Implementation with Monitors

Lock lock; CV emptyCV, fullCV; produce(item) { lock.acquire(); // get lock while (queue.size() == MAX) // wait until there is fullCV.wait(&lock); // space queue.enqueue(item); emptyCV.signal(); // signal waiting costumer lock.release(); // release lock } consume() { lock.acquire(); // get lock while (queue.isEmpty()) // wait until there is item emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); // signal waiting producer lock.release(); // release lock return item; }

slide-25
SLIDE 25

Question

  • Does kth call to consume() return item put by kth call to

produce()?

  • No! thread calling wait() must re-acquire lock after waking up;

before woken-up thread re-acquire lock, a newly arrived thread could acquire lock and consume item

slide-26
SLIDE 26

Mesa vs. Hoare Monitors

  • Consider piece of consume() code

while (queue.isEmpty()) emptyCV.wait(&lock);

  • Why didn’t we do this?

if (queue.isEmpty()) emptyCV.wait(&lock);

  • Answer: it depends on the type of scheduling
  • Hoare-style
  • Mesa-style
slide-27
SLIDE 27

Hoare Monitors

  • Signaler gives up lock and processor to waiter – waiter runs

immediately

  • Waiter gives up lock and processor back to signaler when it

exits critical section or if it waits again

lock.acquire() … if (queue.isEmpty()) emptyCV.wait(&lock); … lock.release(); … lock.acquire() … dataready.signal(); … lock.release();

lock & CPU lock & CPU

slide-28
SLIDE 28

Mesa Monitors

  • Signaler keeps lock and processor
  • Waiter placed on ready queue with no special priority
  • Practically, need to check condition again after wait
  • Most real operating systems

Put waiting thread

  • n ready queue

s c h e d u l e w a i t i n g t h r e a d lock.acquire() … while (queue.isEmpty()) emptyCV.wait(&lock); … lock.release(); … lock.acquire() … dataready.signal(); … lock.release();

slide-29
SLIDE 29

Mesa Monitor: Why “while()”?

  • What if we use “if” instead of “while” in bounded buffer example?

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; } produce(item) { lock.acquire(); if (queue.size() == MAX) fullCV.wait(&lock); queue.enqueue(item); emptyCV.signal(); lock.release(); }

Use “if” instead of “while”

slide-30
SLIDE 30

Mesa Monitor: Why “while()”? (cont.)

T1 (Running) queue

lock: FREE emptyCV queue → NULL

Monitor

  • App. Shared State

Running: T1 ready queue → NULL …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

slide-31
SLIDE 31

Mesa Monitor: Why “while()”? (cont.)

T1 (Running) queue

lock: BUSY (T1) emptyCV queue → NULL

Monitor

  • App. Shared State

Running: T1 ready queue → NULL …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

slide-32
SLIDE 32

Mesa Monitor: Why “while()”? (cont.)

T1 (Waiting) queue

lock: FREE emptyCV queue →T1

Monitor

  • App. Shared State

Running: ready queue → NULL …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

wait(&lock) puts thread

  • n emptyCV queue and

releases lock

slide-33
SLIDE 33

Mesa Monitor: Why “while()”? (cont.)

T1 (Waiting) queue

lock: FREE emptyCV queue →T1

Monitor

  • App. Shared State

Running: T2 ready queue → NULL …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; } produce(item) { lock.acquire(); if (queue.size()==MAX) fullCV.wait(&lock); queue.enqueue(item); emptyCV.signal(); lock.release(); }

T2 (Running)

slide-34
SLIDE 34

Mesa Monitor: Why “while()”? (cont.)

T1 (Waiting) queue

lock: BUSY (T2) emptyCV queue →T1

Monitor

  • App. Shared State

Running: T2 ready queue → NULL …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; } produce(item) { lock.acquire(); if (queue.size()==MAX) fullCV.wait(&lock); queue.enqueue(item); emptyCV.signal(); lock.release(); }

T2 (Running)

slide-35
SLIDE 35

Mesa Monitor: Why “while()”? (cont.)

T1 (Ready) queue

lock: BUSY (T2) emptyCV queue → NULL

Monitor

  • App. Shared State

Running: T2 ready queue →T1 …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; } produce(item) { lock.acquire(); if (queue.size()==MAX) fullCV.wait(&lock); queue.enqueue(item); emptyCV.signal(); lock.release(); }

T2 (Running)

signal() wakes up and moves it to ready queue

slide-36
SLIDE 36

Mesa Monitor: Why “while()”? (cont.)

T1 (Ready) queue

lock: BUSY (T2) emptyCV queue → NULL

Monitor

  • App. Shared State

Running: T2 ready queue →T1, T3 …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; } produce(item) { lock.acquire(); if (queue.size()==MAX) fullCV.wait(&lock); queue.enqueue(item); emptyCV.signal(); lock.release(); } consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

T2 (Running) T3 (Ready)

slide-37
SLIDE 37

Mesa Monitor: Why “while()”? (cont.)

T1 (Ready) queue

lock: FREE emptyCV queue → NULL

Monitor

  • App. Shared State

Running: ready queue →T1, T3 …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; } produce(item) { lock.acquire(); if (queue.size()==MAX) fullCV.wait(&lock); queue.enqueue(item); emptyCV.signal(); lock.release(); } consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

T2 (Terminated) T3 (Ready)

slide-38
SLIDE 38

Mesa Monitor: Why “while()”? (cont.)

T1 (Ready) queue

lock: FREE emptyCV queue → NULL

Monitor

  • App. Shared State

Running: T3 ready queue →T1 …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; } consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

T3 (Running)

T3 is scheduled first

slide-39
SLIDE 39

Mesa Monitor: Why “while()”? (cont.)

T1 (Ready) queue

lock: BUSY (T3) emptyCV queue → NULL

Monitor

  • App. Shared State

Running: T3 ready queue →T1 …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; } consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

T3 (Running)

slide-40
SLIDE 40

Mesa Monitor: Why “while()”? (cont.)

T1 (Ready) queue

lock: BUSY (T3) emptyCV queue → NULL

Monitor

  • App. Shared State

Running: T3 ready queue →T1 …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; } consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

T3 (Running)

slide-41
SLIDE 41

Mesa Monitor: Why “while()”? (cont.)

T1 (Ready) queue

lock: FREE emptyCV queue → NULL

Monitor

  • App. Shared State

Running: ready queue →T1 …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; } consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

T3 (Terminated)

slide-42
SLIDE 42

Mesa Monitor: Why “while()”? (cont.)

T1 (Running) queue

lock: BUSY (T1) emptyCV queue → NULL

Monitor

  • App. Shared State

Running: T1 ready queue → NULL …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

slide-43
SLIDE 43

Mesa Monitor: Why “while()”? (cont.)

T1 (Running) queue

lock: BUSY (T1) emptyCV queue → NULL

Monitor

  • App. Shared State

Running: T1 ready queue → NULL …

CPU State

consume() { lock.acquire(); if (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

Error!

slide-44
SLIDE 44

Mesa Monitor: Why “while()”? (cont.)

T1 (Running) queue

lock: BUSY (T1) emptyCV queue → NULL

Monitor

  • App. Shared State

Running: T1 ready queue → NULL …

CPU State

consume() { lock.acquire(); while (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

Check again if empty!

slide-45
SLIDE 45

Mesa Monitor: Why “while()”? (cont.)

T1 (Waiting) queue

lock: FREE emptyCV queue →T1

Monitor

  • App. Shared State

Running: ready queue → NULL …

CPU State

consume() { lock.acquire(); while (queue.isEmpty()) emptyCV.wait(&lock); item = queue.dequeue(); fullCV.signal(); lock.release(); return item; }

slide-46
SLIDE 46

Mesa Monitor: Why “while()”? (cont.)

When waiting upon a Condition, a spurious wakeup is permitted to occur, in general, as a concession to the underlying platform semantics. This has little practical impact on most application programs as a Condition should always be waited upon in a loop, testing the state predicate that is being waited for From Java User Manual

slide-47
SLIDE 47

Condition Variable vs. Semaphore

  • CV’s signal() has no memory
  • If signal() is called before wait(), then signal is waisted
  • Semaphore’s V() has memory
  • If V() is called before P(), P() will not wait
  • Generally, it’s better to use monitors but not always
  • Example: interrupt handlers
  • Shared memory is read/written concurrently by HW and kernel
  • HW cannot use SW locks
  • Kernel thread checks for data and calls wait() if there is no data
  • HW write to shared memory, starts interrupt handler to then call signal()
  • This is called naked notify because interrupt handler doesn’t hold lock (why?)
  • This may not work if signal comes before kernel thread calls wait
  • Common solution is to use semaphores instead
slide-48
SLIDE 48

Implementing Condition Variable using Semaphores (Take 1)

  • Does this work?
  • No! signal() should not have memory!

wait(lock) { lock.release(); semaphore.P(); lock.acquire(); } signal() { semaphore.V(); }

slide-49
SLIDE 49

Implementing Condition Variable using Semaphores (Take 2)

  • Does this work?
  • No! For one, not legal to look at contents of semaphore’s queue.
  • But also, releasing lock and going to sleep should happen atomically –

signaler can slip in after lock is released, and before waiter is put on wait queue, which means waiter never wakes up! wait(lock) { lock.release(); semaphore.P(); lock.acquire(); } signal() { if (semaphore’s queue is not empty) semaphore.V(); }

slide-50
SLIDE 50

Implementing Condition Variable using Semaphores (Take 3)

Key idea: have separate semaphore for each waiting thread and put semaphores in ordered queue

wait(lock) { semaphore = new Semaphore; // a semaphore per waiting thread queue.enqueue(semaphore); // queue for waiting threads lock.release(); semaphore.P(); lock.acquire(); } signal() { if (!queue.isEmpty()) { semaphore = queue.dequeu() semaphore.V(); } }

slide-51
SLIDE 51

Readers/Writers Lock

  • Motivation: consider shared database with two classes of users
  • Readers: never modify database
  • Writers: read and modify database
  • Database can have many readers at the same time
  • But there can be only one writer active at a time

R W R W

slide-52
SLIDE 52

Properties of Readers/Writers Lock

  • Common variant of mutual exclusion
  • One writer at a time, if no readers
  • Many readers, if no writer
  • Correctness constraints
  • Readers can read when no writers
  • Writers can read/write when no readers or writers
  • Only one thread manipulates state of the lock at a time

Thread 2 Thread 1 Writer Reader Writer NO! NO! Reader NO! OK! OK!

slide-53
SLIDE 53

Readers/Writers Lock Class

class ReadWriteLock { private: Lock lock; // needed to change state vars CV okToRead // CV for readers CV okToWrite; // CV for writers int AW = 0; // # of active writers int AR = 0; // # of active readers int WW = 0; // # of waiting writers int WR = 0; // # of waiting readers public: void acquireRL(); void releaseRL(); void acquireWL(); void releaseWL(); }

slide-54
SLIDE 54

Readers/Writers Lock Design Pattern

read() { lock.acquireRL(); // Read shared state lock.releaseRL(); } write() { lock.acquireWL(); // Read/write shared state lock.releaseWL(); }

slide-55
SLIDE 55

Readers/Writers Lock Implementation

acquireRL() { lock.acquire(); // Need lock to change state vars while (AW + WW > 0) { // Is it safe to read? WR++; // No! add to # of waiting readers

  • kToRead.wait(&lock);

// Wait on condition variable WR--; // No longer waiting } AR++; // Now we are active again lock.release(); } releaseRL() { lock.acquire(); AR--; // No longer active if (AR == 0 && WW > 0) // If no active reader,

  • kToWrite.signal();

// wake up waiting writer lock.release(); }

Why release lock here?

Lock is acquired to change internal state of R/W lock

slide-56
SLIDE 56

Readers/Writers Lock Implementation (cont.)

acquireWL() { lock.acquire(); while (AW + AR > 0) { // is it safe to write? WW++; // No! add to # of waiting writers

  • kToWrite.wait(&lock);

// Wait on condition variable WW--; // No longer waiting } AW++; // Now we are active again lock.release(); } releaseWL() { lock.acquire(); AW--; // No longer active if (WW > 0) { // Give priority to writers

  • kToWrite.signal();

// Wake up a waiting writer } else if (WR > 0) { // If there are waiting readers,

  • kToRead.broadcast();

// wake them all up } lock.release(); }

slide-57
SLIDE 57

Simulation of Readers/Writers Lock

  • Consider following sequence of arrivals: R1, R2, W1, R3

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); } write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-58
SLIDE 58

Simulation of Readers/Writers Lock

  • R1 comes – AR = 0, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-59
SLIDE 59

Simulation of Readers/Writers Lock

  • R1 comes – AR = 0, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-60
SLIDE 60

Simulation of Readers/Writers Lock

  • R1 comes – AR = 0, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-61
SLIDE 61

Simulation of Readers/Writers Lock

  • R1 comes – AR = 1, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-62
SLIDE 62

Simulation of Readers/Writers Lock

  • R1 comes – AR = 1, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-63
SLIDE 63

Simulation of Readers/Writers Lock

  • R1 comes – AR = 1, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-64
SLIDE 64

Simulation of Readers/Writers Lock

  • R2 comes – AR = 1, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-65
SLIDE 65

Simulation of Readers/Writers Lock

  • R2 comes – AR = 1, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-66
SLIDE 66

Simulation of Readers/Writers Lock

  • R2 comes – AR = 1, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-67
SLIDE 67

Simulation of Readers/Writers Lock

  • R2 comes – AR = 2, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-68
SLIDE 68

Simulation of Readers/Writers Lock

  • R2 comes – AR = 2, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-69
SLIDE 69

Simulation of Readers/Writers Lock

  • R2 comes – AR = 2, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

Assume readers take a while to access database Situation: Locks released, only AR is non-zero

slide-70
SLIDE 70

Simulation of Readers/Writers Lock

  • W1 comes – AR = 2, WR = 0, AW = 0, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-71
SLIDE 71

Simulation of Readers/Writers Lock

  • W1 comes – AR = 2, WR = 0, AW = 0, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-72
SLIDE 72

Simulation of Readers/Writers Lock

  • W1 comes – AR = 2, WR = 0, AW = 0, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-73
SLIDE 73

Simulation of Readers/Writers Lock

  • W1 comes – AR = 2, WR = 0, AW = 0, WW = 1

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-74
SLIDE 74

Simulation of Readers/Writers Lock

  • W1 comes – AR = 2, WR = 0, AW = 0, WW = 1

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

W1 cannot start because of readers, so it releases lock and goes to sleep

slide-75
SLIDE 75

Simulation of Readers/Writers Lock

  • R3 comes – AR = 2, WR = 0, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-76
SLIDE 76

Simulation of Readers/Writers Lock

  • R3 comes – AR = 2, WR = 0, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-77
SLIDE 77

Simulation of Readers/Writers Lock

  • R3 comes – AR = 2, WR = 0, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-78
SLIDE 78

Simulation of Readers/Writers Lock

  • R3 comes – AR = 2, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-79
SLIDE 79

Simulation of Readers/Writers Lock

  • R3 comes – AR = 2, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

Status:

  • R1 and R2 still reading
  • W1 and R3 waiting on okToWrite

and okToRead, respectively

slide-80
SLIDE 80

Simulation of Readers/Writers Lock

  • R2 is done reading – AR = 2, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-81
SLIDE 81

Simulation of Readers/Writers Lock

  • R2 is done reading – AR = 1, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-82
SLIDE 82

Simulation of Readers/Writers Lock

  • R2 is done reading – AR = 1, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-83
SLIDE 83

Simulation of Readers/Writers Lock

  • R2 is done reading – AR = 1, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-84
SLIDE 84

Simulation of Readers/Writers Lock

  • R1 is done reading – AR = 1, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-85
SLIDE 85

Simulation of Readers/Writers Lock

  • R1 is done reading – AR = 0, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-86
SLIDE 86

Simulation of Readers/Writers Lock

  • R1 is done reading – AR = 0, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-87
SLIDE 87

Simulation of Readers/Writers Lock

  • R1 is done reading – AR = 0, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

All readers are finished, R1 signals waiting writer – note, R3 is still waiting

slide-88
SLIDE 88

Simulation of Readers/Writers Lock

  • R1 is done reading – AR = 0, WR = 1, AW = 0, WW = 1

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-89
SLIDE 89

Simulation of Readers/Writers Lock

  • W1 gets a signal – AR = 0, WR = 1, AW = 0, WW = 1

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

W1 gets signal from R1

slide-90
SLIDE 90

Simulation of Readers/Writers Lock

  • W1 gets a signal – AR = 0, WR = 1, AW = 0, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-91
SLIDE 91

Simulation of Readers/Writers Lock

  • W1 gets a signal – AR = 0, WR = 1, AW = 1, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-92
SLIDE 92

Simulation of Readers/Writers Lock

  • W1 gets a signal – AR = 0, WR = 1, AW = 1, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-93
SLIDE 93

Simulation of Readers/Writers Lock

  • W1 gets a signal – AR = 0, WR = 1, AW = 1, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-94
SLIDE 94

Simulation of Readers/Writers Lock

  • W1 is done – AR = 0, WR = 1, AW = 1, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-95
SLIDE 95

Simulation of Readers/Writers Lock

  • W1 is done – AR = 0, WR = 1, AW = 0, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-96
SLIDE 96

Simulation of Readers/Writers Lock

  • W1 is done – AR = 0, WR = 1, AW = 0, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-97
SLIDE 97

Simulation of Readers/Writers Lock

  • W1 is done – AR = 0, WR = 1, AW = 0, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-98
SLIDE 98

Simulation of Readers/Writers Lock

  • W1 is done – AR = 0, WR = 1, AW = 0, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

No waiting writer, so

  • nly signal R3
slide-99
SLIDE 99

Simulation of Readers/Writers Lock

  • W1 is done – AR = 0, WR = 1, AW = 0, WW = 0

write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

slide-100
SLIDE 100

Simulation of Readers/Writers Lock

  • R3 gets a signal – AR = 0, WR = 1, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

R3 gets signal from W3

slide-101
SLIDE 101

Simulation of Readers/Writers Lock

  • R3 gets a signal – AR = 0, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-102
SLIDE 102

Simulation of Readers/Writers Lock

  • R3 gets a signal – AR = 1, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-103
SLIDE 103

Simulation of Readers/Writers Lock

  • R3 finishes – AR = 0, WR = 0, AW = 0, WW = 0

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); }

slide-104
SLIDE 104

Readers/Writers Lock Questions

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); } write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

What if we remove this line? It works but it’s inefficient, writer wakes up and goes to sleep again when it’s not save to write

slide-105
SLIDE 105

Readers/Writers Lock Questions

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.broadcast();

lock.release(); } write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

What if we turn

signal() to broadcast()?

It works but it’s inefficient to wake up all writers

  • nly for one to becomes active
slide-106
SLIDE 106

Readers/Writers Lock Questions

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToContinue.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToContinue.signal();

lock.release(); } write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToContinue.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToContinue.signal();

} else if (WR > 0) {

  • kToContinue.broadcast();

} lock.release(); }

What if we turn okToWrite and

  • kToRead into okContinue?

Signal could be delivered to wrong thread (reader) and get waisted!

slide-107
SLIDE 107

Readers/Writers Lock Questions

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToContinue.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToContinue.broadcast();

lock.release(); } write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToContinue.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToContinue.broadcast();

} else if (WR > 0) {

  • kToContinue.broadcast();

} lock.release(); }

Does changing signal() to broadcast() solve the problem? Yes, but it’s inefficient to wake up all threads for only one to becomes active

slide-108
SLIDE 108

Readers/Writers Lock Questions

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); } write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

Can readers starve? Yes: writers take priority

slide-109
SLIDE 109

Readers/Writers Lock Questions

read() { lock.acquire(); while (AW + WW > 0) { WR++;

  • kToRead.wait(&lock);

WR--; } AR++; lock.release(); // Read lock.acquire(); AR--; if (AR == 0 && WW > 0)

  • kToWrite.signal();

lock.release(); } write() { lock.acquire(); while (AW + AR > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); // Read and Write lock.acquire(); AW--; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

Can writers starve? Yes: a waiting writer may not be able to proceed if another writer slips in between signal and wakeup

slide-110
SLIDE 110

Readers/Writers Lock Without Writer Starvation (Take 1)

acquireWL() { lock.acquire(); while (AW + AR + WW > 0) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); }

  • Does this work?
  • No! If there WW is more than zero, then no waiting writer can

successfully proceed

check also for waiting writers

slide-111
SLIDE 111

Readers/Writers Lock Without Writer Starvation (Take 2)

numWriters = 0; nextToGo = 1; acquireWL() { lock.acquire(); myPos = numWriters++; while (AW + AR > 0 || myPos > nextToGo) { WW++;

  • kToWrite.wait(&lock);

WW--; } AW++; lock.release(); } releaseWL() { lock.acquire(); AW--; nextToGo++; if (WW > 0) {

  • kToWrite.signal();

} else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); }

Idea: keep track of writers’ waiting order, allow writer with longest waiting time to proceed

Does this work? No, Signal can wake up a wrong writer and get waisted! Inefficient solution is to change

signal() to broadcast()

slide-112
SLIDE 112

Readers/Writers Lock Without Writer Starvation (Take 3)

numWriters = 0; nextToGo = 1; acquireWL() { lock.acquire(); myPos = numWriters++; myCV = new CV(); queue.enqueue(myCV); while (AW + AR > 0 || myPos > nextToGo) { WW++; myCV.wait(&lock); WW--; } AW++; queue.dequeue(); lock.release(); } releaseWL() { lock.acquire(); AW--; nextToGo++; if (WW > 0) { queue.head().signal(); } else if (WR > 0) {

  • kToRead.broadcast();

} lock.release(); } releaseRL() { lock.acquire(); AR--; if (AR == 0 && WW > 0) queue.head().signal(); lock.release(); }

Idea for efficient solution: have separate CV for each waiting writer and put CV’s in ordered queue

slide-113
SLIDE 113

Communicating Sequential Processes (CSP/Google Go)

  • Instead of allowing threads to access shared objects, each object is assigned

to single corresponding thread

  • Only one thread is allowed to access object’s data
  • Threads communicate with each other solely through message-passing
  • Instead of calling method on shared object, threads send messages to object’s

corresponding thread with method name and arguments

  • Thread waits in a loop, gets messages, and performs operations
  • No race condition!
slide-114
SLIDE 114

Bounded Buffer Implementation with CSP

while (msg = getNext()) { if (msg == GET) { if (!queue.isEmpty()) { // get item // send reply // if pending put, do it // and send reply } else { // queue get operation } } } else if (msg == PUT) { if (queue.size() < MAX) { // put item // send reply // if pending get, do it // and send reply } else { // queue put operation } } }

slide-115
SLIDE 115

Locks/CVs vs. CSP

  • Create a lock on shared data

= create a single thread to operate on data

  • Call a method on a shared object

= send a message/wait for reply

  • Wait for a condition

= queue an operation that can’t be completed just yet

  • Signal a condition

= perform a queued operation, now enabled

Execution of procedure with monitor lock is equivalent to processing message in CSP (monitor is, in effect, single-threaded while it is holding lock)

slide-116
SLIDE 116

Summary

  • Semaphores are like integers with only two operations
  • P(): Wait if zero; decrement when becomes non-zero
  • V(): Increment and wake a sleeping task (if exists)
  • Can initialize value to any non-negative value
  • Use separate semaphore for each constraint
  • Monitors have one lock 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()
  • Communicating sequential processes
  • Communicate only via message-passing
slide-117
SLIDE 117

Questions?

globaldigitalcitizen.org

slide-118
SLIDE 118

Acknowledgment

  • Slides by courtesy of Anderson, Culler, Stoica,

Silberschatz, Joseph, and Canny