W4118 Operating Systems Instructor: Junfeng Yang

advertisement
W4118 Operating Systems
Instructor: Junfeng Yang
Logistics


Homework 2 due time: 3:09 pm this
Thursday (one hour before class)
Submit everything electronically at
courseworks, including written assignment
Last lecture

Synchronization


Layered approach to synchronization
Critical section requirements: safe, live, bounded
• Desirable: efficient, fair, simple

Locks
• Uniprocessor implementation: disable and enable
interrupts
• Software-based locks: peterson’s algorithm
• Locks with hardware support
– atomic test_and_set
Today

Lock (wrap up)

Semaphore

Monitor

A classical synchronization problem: read and
write lock
Recall: Spin-wait or block

Spin-lock may waste CPU cycles: lock holder gets
preempted, and scheduled threads try to grab lock


Shouldn’t use spin-lock on single core
On multi-core, good plan is: spin a bit, then yield
Problem with simple yield
lock()
{
}

Problem:




while(test_and_set(&flag))
yield();
Still a lot of context switches; poll for lock
Starvation possible
Why? No control over who gets the lock next
Need explicit control over who gets the lock
Implementing locks: version 4

The idea


Add thread to queue when lock unavailable
In unlock(), wake up one thread in queue
lock() {
if (flag == 1)
add myself to wait queue
yield
…
}

Problem I: may lose the wake up



unlock() {
flag = 0
if(any thread in wait queue)
wake up one wait thread
…
Lock from a third
}
thread
Fix: use a spin_lock or lock w/ simple yield!
Doesn’t completely avoid spin-wait, but make wait time
short, thus reasonable
Problem II: may not wake up the right thread

Fix: unlock() directly transfers lock to waiting thread
Implementing locks: version 4, the code
typedef struct __mutex_t {
int flag;
// 0: mutex is available, 1: mutex is not available
int guard;
// guard lock to avoid losing wakeups
queue_t *q; // queue of waiting threads
} mutex_t;
void lock(mutex_t *m) {
while (test_and_set(m->guard))
; //acquire guard lock by spinning
if (m->flag == 0) {
m->flag = 1; // acquire mutex
m->guard = 0;
} else {
enqueue(m->q, self);
m->guard = 0;
yield();
}
}

void unlock(mutex_t *m) {
while (test_and_set(m->guard))
;
if (queue_empty(m->q))
// release mutex; no one wants mutex
m->flag = 0;
else
// direct transfer mutex to next thread
wakeup(dequeue(m->q));
m->guard = 0;
}
This is very close to real mutex implementations
Today

Lock (wrap up)

Semaphore

Monitor

A classical synchronization problem: read and
write lock
Semaphore Motivation


Problem with lock: mutual exclusion, but no
ordering; may want more
E.g. Producer-consumer problem





$ cat 1.txt | sort | uniq | wc
Producer: creates a resource
Consumer: uses a resource
bounded buffer between them
Scheduling order: producer waits if buffer full,
consumer waits if buffer empty
Semaphore Definition

A synchronization variable that:

Contains an integer value
• Can’t access directly
• Must initialize to some value
– sem_init(sem_t *s, int pshared, unsigned int value)

Has two operations to manipulate this integer
• sem_wait, or down(), P() (comes from Dutch)
• sem_post, or up(), V() (comes from Dutch)
int sem_wait(sem_t *s) {
wait until value of semaphore s
is greater than 0
decrement the value of
semaphore s by 1
}
int sem_post(sem_t *s) {
increment the value of
semaphore s by 1
if there are 1 or more
threads waiting, wake 1
}
Semaphore Uses

// initialize to X
sem_init(s, 0, X)
Mutual exclusion


Semaphore as mutex
What should initial value be?
• Binary semaphore: X=1
• ( Counting semaphore: X>1 )

sem_wait(s);
// critical section
sem_post(s);
Scheduling order


One thread waits for another
What should initial value be?
//thread 0
… // 1st half of computation
sem_post(s);
// thread 1
sem_wait(s);
… //2nd half of computation
Producer-Consumer (Bounded-Buffer)
Problem

Bounded buffer: size ‘N’


Producer process writes data to buffer


Access entry 0… N-1, then “wrap around” to 0 again
Must not write more than ‘N’ items more than consumer “ate”
Consumer process reads data from buffer

Should not try to consume if there is no data
0
1
N-1
Producer
Consumer
Solving Producer-Consumer problem

Two semaphores



sem_t full; // # of filled slots
sem_t empty; // # of empty slots
Problem: mutual exclusion?
sem_init(&full, 0, 0);
sem_init(&empty, 0, N);
producer() {
sem_wait(empty);
… // fill a slot
sem_post(full);
}
consumer() {
sem_wait(full);
… // empty a slot
sem_post(empty);
}
Solving Producer-Consumer problem: Final

Three semaphores



sem_t full; // # of filled slots
sem_t empty; // # of empty slots
sem_t mutex; // mutual exclusion
sem_init(&full, 0, 0);
sem_init(&empty, 0, N);
sem_init(&mutex, 0, 1);
producer() {
sem_wait(empty);
sem_wait(&mutex);
… // fill a slot
sem_post(&mutex);
sem_post(full);
}
consumer() {
sem_wait(full);
sem_wait(&mutex);
… // empty a slot
sem_post(&mutex);
sem_post(empty);
}
How to Implement Semaphores?

Part of your next programming assignment
Today

Lock (wrap up)

Semaphore

Monitor

A classical synchronization problem: read and
write lock
Monitors

Background




Concurrent programming meets object-oriented
programming
When concurrent programming became a big deal, objectoriented programming too
People started to think about ways to make concurrent
programming more structured
Monitor: object with a set of monitor procedures
and only one thread may be active (i.e. running one
of the monitor procedures) at a time
Schematic view of a Monitor


Can think of a
monitor as one big
lock for a set of
operations/ methods
In other words, a
language
implementation of
mutexes
How to Implement Monitor?
Compiler automatically inserts lock and unlock operations
upon entry and exit of monitor procedures
class account {
int balance;
public synchronized void deposit() {
++balance;
}
public synchronized void withdraw() {
--balance;
}
};
lock(m);
++balance;
unlock(m);
lock(m);
--balance;
unlock(m);
Condition Variables

Need wait and wakeup as in semaphores

Monitor uses Condition Variables


Conceptually associated with some conditions
Operations on condition variables:



wait(): suspends the calling thread and releases the
monitor lock. When it resumes, reacquire the lock. Called
with condition is not true
signal(): resumes one thread (if any) waiting in wait().
Called when condition becomes true
broadcast(): resumes all threads waiting in wait()
Monitor with Condition Variables
Subtle Differences between condition
variables and semaphores


Semaphores are sticky: they have memory,
sem_post() will increment the semaphore, even
if no one has called sem_wait()
Condition variables are not: if no one is waiting
for a signal(), this signal() is not saved
Producer-Consumer with Monitors
monitor ProducerConsumer {
int nfull = 0;
cond notfull, notempty;
producer() {
if (nfull == N)
wait (notfull);
… // fill a slot
++ nfull;
signal (notempty);
}
};
consumer() {
if (nfull == 0)
wait (notempty);
… // empty a slot
-- nfull
signal (notfull);
}

nfull: number of filled
buffers


Need to do our own
counting for condition
variables
notfull and notempty:
two condition variables


notfull: not all slots are
full
notempty: not all slots
are empty
Condition Variable Semantics


Problem: when signal() wakes up a waiting thread,
which thread to run inside the monitor, the signaling
thread, or the waiting thread?
Hoare semantics: suspends the signaling thread, and
immediately transfers control to the woken thread


Difficult to implement in practice
Mesa semantics: signal() moves a single waiting thread
from the blocked state to a runnable state, then the
signaling thread continues until it exits the monitor


Easy to implement
Problem: race! E.g. before a woken consumer continues,
another consumer comes in and grabs the buffer
Fixing the Race in Mesa Monitors
monitor ProducerConsumer {
int nfull = 0;
cond notfull, notempty;
producer() {
while (nfull == N)
wait (notfull);
… // fill slot
++ nfull;
signal (notempty);
}
};
consumer() {
while (nfull == 0)
wait (notempty);
… // empty slot
-- nfull
signal (notfull);
}


The fix: when woken, a
thread must recheck the
condition it was waiting on
Most systems use mesa
semantics


E.g. pthread
Thus, you should remember
to recheck
Monitor with pthread
class ProducerConsumer {
int nfull = 0;
pthread_mutex_t m;
pthread_cond_t notfull, notempty;
public:
producer() {
pthread_mutex_lock(&m);
while (nfull == N)
ptherad_cond_wait (&notfull, &m);
… // fill slot
++ nfull;
pthread_cond_signal (notempty);
pthread_mutex_unlock(&m);
}
…
};


C/C++ don’t provide
monitors; but we can
implement monitors using
pthread mutex and
condition variable
For producer-consumer
problem, need 1 pthread
mutex and 2 pthread
condition variables
(pthread_cond_t)

Manually lock and unlock
mutex for monitor procedures

pthread_cond_wait (cv, m):
atomically waits on cv and
releases m

Why atomically? You figure
out
Today

Lock (wrap up)

Semaphore

Monitor

A classical synchronization problem: read and
write lock
Readers-Writers Problem
Courtois et al 1971
 Models access to a database




A reader is a thread that needs to look at the
database but won’t change it.
A writer is a thread that modifies the database
Example: making an airline reservation


When you browse to look at flight schedules
the web site is acting as a reader on your
behalf
When you reserve a seat, the web site has to
write into the database to make the reservation
Solving Readers-Writers w/ Regular Lock
sem_t lock;
Writer
sem_wait (lock);
...
// write shared data
...
sem_post (lock);


Reader
sem_wait (lock);
...
// read shared data
...
sem_post(lock);
Problem: unnecessary synchronization
 Only one writer can be active at a time
 However, any number of readers can be active
simultaneously !
Solution:

Idea: differentiate lock for read and lock for write
Readers-Writers Lock
int nreader = 0;
sem_t lock = 1, write_lock = 1;
Writer
sem_wait (write_lock);
...
// write shared data
...
sem_post (write_lock);
Problem: may starve writer
How to fix? Not that
straightforward. You figure
out
Reader
sem_wait (lock);
++ nreader;
if (nreader == 1) // first reader
sem_wait (write_lock);
sem_post (lock);
...
// read shared data
...
sem_wait (lock);
-- nreader;
if (nreader == 0) // last reader
sem_post (write_lock);
sem_post (lock);
Download