Semaphores, Condition Variables, and Monitors
CS-446/646
C. Papachristos
Robotic Workers (RoboWork) Lab
University of Nevada, Reno
Semaphores
Semaphore Motivation
Problem with Lock:
Producer-Consumer problem: Ensuring execution order makes sense
CS446/646 C. Papachristos
Semaphores
Semaphore Definition
Abstract data type (i.e. a high-level mechanism) to provide Synchronization
A Synchronization object that contains an Integer Counter variable
CS446/646 C. Papachristos
int sem_wait(sem_t *s) {
// 1. wait until value of
// semaphore s becomes > 0
// 2. decrement value by 1
}
int sem_post(sem_t *s) {
// 1. increment value of s by 1
// 2. if there are 1 or more
// threads waiting, wakeup 1
}
Semaphores
Blocking in Semaphores
Associated with each Semaphore is a Queue of waiting Threads
When P() / sem_wait() is called by a Thread:
When V() / sem_post() Opens the Semaphore:
CS446/646 C. Papachristos
Semaphores
Semaphore Types
Mutex Semaphore (or Binary Semaphore)
Counting Semaphore (or General Semaphore)
CS446/646 C. Papachristos
int sem_init(sem_t *sem,
int pshared,
unsigned int value);
Initializes the Semaphore at sem.�value specifies the initial value for it.
pshared indicates whether this Semaphore is to be shared between the Threads of a Process, or between Processes (sem can be placed in a region of Shared Memory).
int sem_post(sem_t *sem);
Increments (Unlocks) the Semaphore at sem.
int sem_wait(sem_t *sem);
Decrements (Locks) the Semaphore at sem.
If the Semaphore currently has the value zero, then the call Blocks until either it becomes possible to perform the decrement, or a Signal handler interrupts the call.
sem_init(s, 0, X )
sem_wait(s);
// critical section
sem_post(s);
…
Note:
No direct access to counter
Semaphores
CS446/646 C. Papachristos
sem_wait(s);
// critical section
sem_post(s);
sem_init(s, pshared:0 or 1, value:1)
sem_wait(s);
// critical section
sem_post(s);
// 1st half�// of computation
sem_post(s);
sem_wait(s);
// 2nd half
// of computation
sem_init(s, pshared:0 or 1, value:0)
Semaphores
Producer-Consumer (Bounded-Buffer) Problem
Bounded Buffer
Producer Thread : Writes data to Bounded Buffer
Consumer Thread : Reads data from Bounded Buffer
Execution Ordering constraints:
CS446/646 C. Papachristos
0 | 1 | | | | | | | | N-1 |
Producer
Consumer
Semaphores
Producer-Consumer (Bounded-Buffer) Problem
Solution – 1st version
Two Semaphores
CS446/646 C. Papachristos
sem_init(&filled, 0, 0 );
sem_init(&empty, 0, N );
void* producer(void* arg) {
sem_wait(&empty);
… // fill a slot
sem_post(&filled);
}
void* consumer(void* arg) {
sem_wait(&filled);
… // empty a slot
sem_post(&empty);
}
Note:
Sequencing operations
Semaphores
Producer-Consumer (Bounded-Buffer) Problem
Solution – Final version
Three Semaphores
CS446/646 C. Papachristos
sem_init(&filled, 0, 0);
sem_init(&empty, 0, N);
sem_init(&mutex, 0, 1); // 1: binary sem
void* producer(void* arg) {
sem_wait(&empty);
sem_wait(&mutex);
… // fill a slot
sem_post(&mutex);
sem_post(&filled);
}
void* consumer(void* arg) {
sem_wait(&filled);
sem_wait(&mutex);
… // empty a slot
sem_post(&mutex);
sem_post(&empty);
}
Note: Can also use a pthread_mutex_t
Note:
Fill / Empty operations correspond to manipulating the Circular Buffer’s head & tail
Data Structure�“internal access”�Critical Section
Condition Variables
Condition Variables
A Synchronization object that is associated to a Condition Predicate
Operations on Condition Variables :
wait()
Suspends the calling Thread until another Thread signal()s/broadcast()s this Condition Variable
signal()
Resumes one Thread waiting in wait(), if any
broadcast(): Resumes all Threads waiting in wait()
CS446/646 C. Papachristos
Condition Variables
Condition Variables
Although operations have similar names with Semaphores, they are different
wait(): Blocks the calling Thread
signal(): Causes a wait()ing Thread to Wakeup
CS446/646 C. Papachristos
Condition Variables
Producer-Consumer (Bounded-Buffer) Problem
Producer-Consumer with CVs
CS446/646 C. Papachristos
int nfilled = 0;
cond has_empty, has_filled;
void produce() {
if (nfilled == N)
wait (has_empty);
… // fill a slot
++ nfilled;
signal (has_filled);
}
void consume() {
if (nfilled == 0)
wait (has_filled);
… // empty a slot
-- nfilled;
signal (has_empty);
}
Solution with two Condition Variables:
nfilled: Number of filled slots
E.g.:
I.e. (each) Condition Variable also has to have a Queue
Condition Variables
Condition Variable signal() Semantics
When signal() wakes up a wait()ing Thread, who should get to run?
Hoare Semantics:�Suspends Signaler, and immediately (and Atomically) transfers control to a Waiter
Mesa Semantics
Signal moves a single Waiter from the Blocked to the Runnable State, and the Signaler resumes
C. Papachristos
(multiple awakenings on a Multi-processor system, Priority Scheduling need for Priority Inheritance, saving/ restoring Monitor Invariants, etc.)
Condition Variables
Producer-Consumer (Bounded-Buffer) Problem
Producer-Consumer with CVs
CS446/646 C. Papachristos
int nfilled = 0;
cond has_empty, has_filled;
void produce() {
while (nfilled == N)
wait (has_empty);
… // fill a slot
++ nfilled;
signal (has_filled);
}
void consume() {
while (nfilled == 0)
wait (has_filled);
… // empty a slot
-- nfilled;
signal (has_empty);
}
Spurious Wakeup – pthread
Spurious Wakeup Fix:
Condition Variables
Condition Variables with pthread
Producer-Consumer with CVs
CS446/646 C. Papachristos
int nfull = 0;
pthread_mutex_t mut;
pthread_cond_t has_empty,
has_full;
void produce() {
pthread_mutex_lock(&mut);
while (nfull == N)
pthread_cond_wait(&has_empty,
&mut);
… // fill slot
++ nfull;
pthread_cond_signal(has_full);
pthread_mutex_unlock(&mut);
}
…
Unlocks
Mutex
Unlocks
Mutex
pthread’s implementation of pthread_cond_t (Condition Variable) operations requires a pthread_mutex_t (Mutex)
The function shall Block on a Condition Variable. It shall be called with mutex Locked by the calling Thread or Undefined Behavior (!) results.
The function atomically Releases mutex and causes the calling Thread to Block on cond… Upon successful return, the mutex shall have been Locked and shall be owned by the calling Thread.
Condition Variables
Condition Variables with pthread
Producer-Consumer with CVs
CS446/646 C. Papachristos
int nfull = 0;
pthread_mutex_t mut;
pthread_cond_t has_empty,
has_full;
void produce() {
pthread_mutex_lock(&mut);
while (nfull == N)
pthread_cond_wait(&has_empty,
&mut);
… // fill slot
++ nfull;
pthread_cond_signal(has_full);
pthread_mutex_unlock(&mut);
}
…
pthread’s implementation of pthread_cond_t (Condition Variable) operations requires a pthread_mutex_t (Mutex)
The function shall Unblock at least one of the Threads that are Blocked on the specified Condition Variable cond… may be called by a Thread whether or not it currently owns the Mutex that Threads calling pthread_cond_wait() … have associated with the Condition Variable… however, if predictable Scheduling behaviour is required, then that Mutex is Locked by the pthread_cond_signal()-calling Thread
Note: Unlock the Mutex after calling pthread_cond_signal()
Monitors
Semaphore & Condition Variable Summary
CS446/646 C. Papachristos
Monitors
Monitors
An Object-Oriented Language construct that controls access to shared data
A module that encapsulates
CS446/646 C. Papachristos
Monitors
Monitors
A Monitor aims to guarantee Mutual Exclusion
Note: A Monitor Invariant is a safety property associated with the Monitor
CS446/646 C. Papachristos
Monitors
Monitors
A Monitor is like one big Super-Lock for a set of operations/methods
CS446/646 C. Papachristos
monitor account {
int balance;
public void deposit() {
++balance;
}
public void withdraw() {
--balance;
}
};
lock(this.mut);
++balance;
unlock(this.mut);
lock(this.mut);
--balance;
unlock(this.mut);
Note: But check out C++20 synchronized and atomic_noexcept/cancel/commit (experimental): https://en.cppreference.com/w/cpp/language/transactional_memory
Monitor
Procedures
Example of (part of) the opera-tions inserted at Compile-Time.
…
…
…
…
Monitors
Monitors & Condition Variables
Remember: A Monitor also needs to take care of Wait, Wakeup, Queueing functionalities
To achieve the above, a Monitor’s implementation can use a known Synchronization mechanism:
Condition Variables
CS446/646 C. Papachristos
Monitors
Condition Variables (with respect to Monitors)
wait()
Suspends the calling Thread and releases the Monitor Lock (when it resumes, it will reacquire the Monitor Lock)
For wait() to be called, the Thread has to already be “inside the Monitor” (hence holds the Monitor Lock)
signal()
Resumes one Thread waiting in wait(), if any
broadcast(): Resumes all Threads waiting in wait()
Remember: Condition Variables are not boolean objects; they are associated with a boolean Condition Predicate
CS446/646 C. Papachristos
Monitors
Monitors & Condition Variables
Producer-Consumer with a Monitor
Note: With significant “hand-waving” …
CS446/646 C. Papachristos
monitor ProducerConsumer {
cond has_empty, has_filled;
bool (*has_empty_test)(void* args);
bool (*has_filled_test)(void* args);
int nfilled = 0;
bool has_empty_impl (void*) {
return nfilled == N;
}
bool has_filled_impl (void*) {
return nfilled == 0;
}
…
…
void produce() {
while ( has_empty_test(NULL) )
wait ( has_empty );
// fill a slot
++ nfilled;
signal ( has_filled );
}
void consume() {
while ( has_filled_test(NULL) )
wait ( has_filled );
// empty a slot
-- nfilled;
signal ( has_empty );
}
};
Note: C/C++ don’t provide Monitors, but we can implement such functionality using Condition Variables
Time for Questions !
CS-446/646
CS446/646 C. Papachristos