CS 5460: Operating Systems
CS5460: Operating Systems Lecture 10:
Semaphores and Classical Synch Problems
(Chapter 6)
CS 5460: Operating Systems
Synchronization So Far… Race conditions Mutual exclusion Data races Requirements for correct synchronization:
– Mutual exclusion – Progress – Bounded wait
Arguing correctness for critical sections Basic solutions to the critical section problem:
– 2-process solution à Peterson, Dekker – N-process solution à Bakery Algorithm – Spinlocks and blocking locks based on HW primitives
CS 5460: Operating Systems
Synchronization So Far… Basically everything we’ve covered so far is about
mutual exclusion, a.k.a. locking But there are plenty of synchronization problems
that require more than just locking For example:
– How do we make a bank account processing thread wait until the account balance is positive?
– How do we make a producer thread wait for a free buffer, and a consumer thread wait for a full buffer?
– How can we implement a “lock” where many reader threads can execute inside the critical section at once, but only one writer?
It can be hard to do jobs like these using just locks Today we’ll look at higher-level sync. primitives
CS 5460: Operating Systems
Semaphores A semaphore is a counter that processes or threads
can manipulate atomically Operations on a semaphore:
– P() or down() or wait() : wait until counter > 0, then atomically decrement it
– V() or up() or signal() or post(): atomically increment counter
Counter generally represents the number of available resources
– Never negative
A semaphore whose counter is always 0 or 1 is called a binary semaphore
– This is just a lock
CS 5460: Operating Systems
Implementing Semaphores Semaphore::P (T:Thread) {
disable interrupts;
while (counter <= 0) {
add T to Q;
T à Sleep();
}
counter--;
enable interrupts;
}
Semaphore::V () {
disable interrupts;
counter++;
if (Q not empty) {
remove T from Q;
put T on readyQ;
}
enable interrupts;
}
Class Semaphore {
public:
void P();
void V();
private:
int counter;
Queue Q;
}
Semaphore::Semaphore (int init) {
counter ß init;
Q ß 0; // Queue empty
}
CS 5460: Operating Systems
Semaphores in Pthreads Pthreads provides semaphores for you Main operations:
– sem_init() – sem_post() – sem_wait() – sem_destroy()
But also: – sem_getvalue() – sem_open() – sem_close() – sem_unlink()
CS 5460: Operating Systems
Bounded Buffer Problem Collection of “producer” and “consumer” threads Fixed-size (e.g., N-entry) shared buffer Producers generate “work” and add to buffer Consumers remove “work” from buffer and do it Need to handle:
– Concurrent threads adding/removing from buffer – Buffer overflow and underflow
CS 5460: Operating Systems
Bounded Buffer Solution BBuffer:AddItem( ) {
entry:
PUT : // Put item in free slot
exit :
}
BBuffer:GetItem( ) {
entry:
GET : // Get item from buffer
exit :
}
Let’s solve this interactively – Hint: Use provided semaphores – What do they mean?
Class BBuffer{
private: semaphore mutex;
semaphore empty;
semaphore full;
int buffer[N];
public: void AddItem( );
void GetItem( );
}
BBuffer::BBuffer ( ) {
mutex = l;
full = 0;
empty = N;
}
CS 5460: Operating Systems
Bounded Buffer Solution BBuffer:AddItem( ) {
entry: P(empty);
P(mutex);
PUT : // Put item in free slot
exit : V(mutex);
V(full);
}
BBuffer:GetItem( ) {
entry: P(full);
P(mutex);
GET : // Get item from buffer
exit : V(mutex);
V(empty);
}
Let’s solve this interactively – Hint: Use provided semaphores – What do they mean?
Class BBuffer{
private: semaphore mutex;
semaphore empty;
semaphore full;
int buffer[N];
public: void AddItem( );
void GetItem( );
}
BBuffer::BBuffer ( ) {
mutex = l;
full = 0;
empty = N;
}
CS 5460: Operating Systems
Readers-Writers Problem
Problem: Shared resource that is “read mostly” – Enforcing strict mutual exclusion may be unacceptable – Want to allow arbitrary number of “readers” concurrently – Only want to allow “writer” if nobody else reading or writing
Allowing multiple readers à important optimization – Many applications have far more readers than writers – Another example of enabling concurrency when possible – All real OS kernels support and use reader/writer locks
How do you know you need a reader / writer lock?
CS 5460: Operating Systems
Readers-Writers Problem ReadWrite::Read() {
entry:
READ: /* Read data */
exit:
}
ReadWrite::Write() {
entry:
WRITE: /* Write data */
exit:
}
class ReadWrite {
public: void Read(), Write();
private: semaphore mutex, writer;
int readers;
}
ReadWrite::ReadWrite {
mutex = writer = 1;
readers = 0;
}
CS 5460: Operating Systems
Readers-Writers v1 ReadWrite::Read() {
entry: P(mutex);
READ: /* Read data */
exit: V(mutex);
}
ReadWrite::Write() {
entry: P(mutex);
WRITE: /* Write data */
exit: V(mutex);
}
class ReadWrite {
public: void Read(), Write();
private: semaphore mutex;
}
ReadWrite::ReadWrite {
mutex = 1;
}
Poor concurrency
CS 5460: Operating Systems
Readers-Writers v2 ReadWrite::Read() {
entry: P(mutex);
if ((++readers) == 1) // 1st
P(writer);
V(mutex);
READ: /* Read data */
exit: P(mutex);
if ((--readers) == 0) // Last
V(writer);
V(mutex);
}
ReadWrite::Write() {
entry: P(writer);
WRITE: /* Write data */
exit: V(writer);
}
Who does this favor? – Read() – | Write() – | X Read() – (Done) X | – … X
class ReadWrite {
public: void Read(), Write();
private: semaphore mutex, writer;
int readers;
}
ReadWrite::ReadWrite {
mutex = writer = 1;
readers = 0;
} Readers preferred – why?
CS 5460: Operating Systems
Readers-Writers v3 ReadWrite::Read() {
entry: P(rw);
READ: /* Read data */
exit: V(rw);
}
ReadWrite::Write() {
entry: for (i=1..MAX_READERS) {
P(rw);
}
WRITE: /* Write data */
exit: for (i=1..MAX_READERS) {
V(rw);
}
}
class ReadWrite {
public: void Read(), Write();
private: semaphore rw;
}
ReadWrite::ReadWrite {
rw = MAX_READERS;
}
Problem?
CS 5460: Operating Systems
Readers-Writers v4 ReadWrite::Read() {
entry: P(rw);
READ: /* Read data */
exit: V(rw);
}
ReadWrite::Write() {
entry: P(mutex);
for (i=1..MAX_READERS)
P(rw);
WRITE: /* Write data */
exit: for (i=1..MAX_READERS)
V(rw);
V(mutex);
}
class ReadWrite {
public: void Read(), Write();
private: semaphore rw, mutex;
}
ReadWrite::ReadWrite {
rw = MAX_READERS;
mutex = 1;
}
CS 5460: Operating Systems
Condition Variable Queue of threads waiting on some “event” inside of
a critical section A condition variable is always paired with a lock Operations:
– Wait(): » Atomically release lock and go to sleep » When thread wakes up, it reacquires the lock
– Signal(): » Wake up thread waiting on event à no-op if nobody is waiting
– Broadcast(): » Wake up all threads waiting on event à no-op if nobody is waiting
CS 5460: Operating Systems
Monitors
Semantics: Only one thread in “object” at a time
– Higher level than locks/sems – Cannot “forget” to lock/unlock – Can ensure mutual exclusion
across multiple code segments – Protects private data of monitor
Java usage – All data must be private – All private methods synchronized
Class ReaderWriter {
private int readcnt = 0;
private int writecnt = 0;
private synchronized ReadStart(){
while (writecnt > 0) Wait();
readcnt++;
}
private synchronized ReadDone(){
if (--readcnt == 0) Notify();
}
public … SomeReadMethod() {
ReadStart();
// Read private data;
ReadDone();
}
CS 5460: Operating Systems
Thread Barriers
Semantics: “N” threads block at barrier until all arrive
Usage: Synchronize between “phases” of parallel programs
– e.g., divide array among threads that use “neighboring” data
– Need neighbors roughly synched
No mutual exclusion
Goal: Maximize throughput – Minimize barrier overhead
int A[N][N];
int mypid;
Barrier B;
…
InitBarrier(&B, N);
…
for (j=0; j < loopcnt; j++){
for (i=0; i < N; i++) {
/* Update my part of A */
A[mypid][i] = …;
}
/* Wait for other threads */
BarrierWait(&B);
}
CS 5460: Operating Systems
Barrier Implementation (simplified) Class Barrier {
public: Wait();
private: semaphore mutex;
int limit, count;
}
Barrier::Barrier(b:int) {
mutex = 1; limit = b;
count = 0;
}
Barrier::Wait() {
P(mutex); count++; V(mutex);
while (count != limit);
}
Simple implementation: – Atomically increment count of
threads waiting at barrier – Spin on global count until it
reaches limit
What performance problems? – Spinning is inefficient – To increment shared count, need
to acquire/release mutex – Every time count is incremented,
need to invalidate copies being spun on (multiprocessor
CS 5460: Operating Systems
Blocking Barrier Implementation
Class Barrier {
public: Wait();
private: semaphore mutex;
int limit, count;
condition cond;
}
Barrier::Barrier(b:int) {
mutex = 1; limit = b;
}
Barrier::Wait() {
P(mutex);
count++;
if (count == limit) {
count = 0;
broadcast (cond);
} else {
wait (cond, mutex);
}
V(mutex);
}
CS 5460: Operating Systems
Questions?