Synchronization Pitfalls & Exercises
CS-446/646
C. Papachristos
Robotic Workers (RoboWork) Lab
University of Nevada, Reno
Synchronization Errors
CS446/646 C. Papachristos
Synchronization Errors
CS446/646 C. Papachristos
void deposit() { // properly synchronized
lock();
++ balance;
unlock();
}
void withdraw() { // no synchronization
-- balance;
}
Remember: Atomic operations
Synchronization Errors
CS446/646 C. Papachristos
void deposit(account_t* acnt) {
lock(acnt->guard);
++ acnt->balance;
unlock(acnt->guard);
}
void withdraw(account_t* acnt) {
lock(acnt->guard);
-- acnt->balance;
unlock(acnt->guard);
}
int balance(account_t* acnt) {
int b;
lock(acnt->guard);
b = acnt->balance;
unlock(acnt->guard);
return b;
}
balance_stamped sum(account_t* a1, account_t* a2) {
balance_stamped sum_stamped;
gettimeofday(&sum_stamped.tv, NULL);
sum_stamped.balance = balance(a1) + balance(a2);
return sum_stamped;
}
void transfer(account_t* a1, account_t* a2) {
withdraw(a1);
deposit(a2);
}
void test() {
int A_bal= A.balance();
transfer(A, A);
assert(A.balance()==A_bal);
}
Synchronization Errors
CS446/646 C. Papachristos
int sum(account *a1, account *a2) {
int s;
lock(a1->guard);
lock(a2->guard);
s = a1->balance;
s += a2->balance;
unlock(a2->guard);
unlock(a1->guard);
return s
}
Note: Can be prevented by a separate sum Lock, or by Static Ordering of Resources (more on that later)
sum(A, B);
Thread 1
sum(B, A);
Thread 2
lock(A->guard);
lock(B->guard);
lock(A->guard);
lock(B->guard);
Block
Block
Synchronization Errors
Example 4-a: Deadlock crossing Abstraction Boundaries
Example when composing operations of Mutex and a Condition Variable:
CS446/646 C. Papachristos
lock(a);
lock(b);
while(!ready)
wait (cv, b);
unlock(b);
unlock(a);
Thread 1
lock(a);
lock(b);
ready = true;
signal (cv);
unlock(b);
unlock(a);
Thread 2
Thread 1 reaches :
wait (cv, b); b Unlocked,
but a Locked;
Thread 2 starts, but will never past its lock(a) to proceed to signal() Thread 1
Mutex
Part
Cond
Var
Part
foo();
bar();
Synchronization Errors
Example 4-b: Monitors also do not compose
CS446/646 C. Papachristos
monitor M1 {
cond_t cv;
foo() {
…
// releases monitor lock
wait(cv);
}
bar() {
…
signal(cv);
}
};
monitor M2 {
f1() {
…
M1.foo();
}
f2() {
…
M1.bar();
}
};
Synchronization Errors
CS446/646 C. Papachristos
void foo() {
pthread_mutex_lock(&mut);
while (value <= 10.5 * 3.14159265358979)
pthread_cond_wait(&cond, &mut);
… // do work
}
void bar() {
… // do work
value += 0.1 * 3.14159265358979 ;
pthread_cond_signal(cond);
}
double value = 0.0; pthread_mutex_t mut; pthread_cond_t cond;
Synchronization Errors
Deadlock Conditions (all need to hold)
Two approaches to dealing with a Deadlock
CS446/646 C. Papachristos
Synchronization Errors
CS446/646 C. Papachristos
Pi
Rj
Pi
Rj
Pi
Rj
Note: Edge removed on unlock()
Note: Request Edge Converted to Assignment Edge on return of lock()
Synchronization Errors
Deadlock Prevention by Elimination of Circular Waiting
Example:
Resource Allocation Graph
CS446/646 C. Papachristos
Synchronization Errors
Deadlock Prevention by Elimination of Circular Waiting
Example:
Resource Allocation Graph
with a definite
Deadlock
Note:
Resources that participate in
Circular Wait are exhausted,
and entirely allocated to the�waiting processes
CS446/646 C. Papachristos
Synchronization Errors
Deadlock Prevention by Elimination of Circular Waiting
Example:
Resource Allocation Graph
and a possible
Deadlock
Note:
Resources that participate in
Circular Wait are exhausted,
but allocated to different�processes than the waiting ones
CS446/646 C. Papachristos
Synchronization Errors
CS446/646 C. Papachristos
Note: E.g. to avoid multi-Mutex Deadlocks, we can make sure that there is a Static Global Order for all Mutexes (How?),�and every program always takes and releases Locks in that order (But also look at C++ std::lock() with Deadlock Avoidance)
Remember: Example 3 where Lock objects a->guard & b->guard are�flipped in the order they are lock()ed by the 2 different Threads
Synchronization Errors
Dealing with a Deadlock
CS446/646 C. Papachristos
Synchronization Errors
Banker’s Algorithm
Classic Deadlock Avoidance approach for Resources with multiple units
1. Assign a Credit Limit to each Customer (Process)
2. Reject any Request that leads to an Unsafe State
3. In practice: System must keep Resource usage well� below capacity to maintain a surplus
CS446/646 C. Papachristos
Process | Allocated | Max | Available |
A B C | A B C | A B C | |
P0 | 0 1 0 | 7 5 3 | 3 3 2 |
P1 | 2 0 0 | 3 2 2 | |
P2 | 3 0 2 | 9 0 2 | |
P3 | 2 1 1 | 2 2 2 | |
P4 | 0 0 2 | 4 3 3 |
*Sudden Request: Customer requests all its Max Resources (but then finishes)
Synchronization Errors
Deadlock Detection & Recovery
Deadlock Detection
CS446/646 C. Papachristos
Synchronization Errors
Deadlock Detection & Recovery
Deadlock Recovery
After a Deadlock has been detected, two main options:
CS446/646 C. Papachristos
Synchronization Errors
Race Detection
Data Race Detection
Will only focus on Data Race Detection
CS446/646 C. Papachristos
Synchronization Errors
Race Detection
Happens-Before relationship & proper Synchronization
Definition: Event A “Happens-Before” Event B if:
CS446/646 C. Papachristos
Synchronization Errors
Race Detection
Violation of Happens-Before for Data Race Detection
Problems:
Note: “any” – approach has no nuance about relationship between shared data and Synch operations
CS446/646 C. Papachristos
Synchronization Errors
CS446/646 C. Papachristos
Synchronization Errors
CS446/646 C. Papachristos
Synchronization Errors
CS446/646 C. Papachristos
Synchronization Errors
CS446/646 C. Papachristos
more strict refinement rule for Write-access
Synchronization Errors
Race Detection
Eraser – Implementation
Pros:
Cons:
CS446/646 C. Papachristos
Synchronization Errors
Race Detection
Eraser – Overview
CS446/646 C. Papachristos
Example Benign Race 1:
“Double-Checks” for Lazy Initialization
if (!init_flag) {
lock();
if (!init_flag) {
my_data = ...;
init_flag = true;
}
unlock();
}
tmp = my_data;
Idea: Faster if init_flag is true most of the time (i.e. data initialized already)
But: Wrong! Compiler/Hardware may reorder lines, loads/stores, etc.
Example Benign Race 2:
“Statistical” counter
++ nrequests;
Even this can happen (speculatively)
Possible
reordering
Synchronization Errors
Memory Synchronization Ordering
Since we touched the subject…
* Each load gets the value of the most recent store in that Total Order.
CS446/646 C. Papachristos
Previous Broken Example:
“Double Checked Locking”
if (!init_flag) {
lock();
if (!init_flag) {
my_data = ...;
init_flag = true;
}
unlock();
}
tmp = my_data;
Wrong! Compiler/Hardware may reorder lines, loads/stores, etc.
Possible
reordering
Even without Compiler reordering, how to guarantee Memory Consistency across multiple Threads of a Multi-Processor system?
Even this can happen (speculatively)
Synchronization Errors
Memory Synchronization Ordering
(e.g. AMD’s Hypertransport, Intel’s QPI (Quick Path Interconnect) & UPI (Ultra Path Interconnect))
CS446/646 C. Papachristos
Synchronization Errors
Memory Synchronization Ordering
i.e. needs to be written-back to Memory at some point in the future, before allowing more reads from Memory
CS446/646 C. Papachristos
Synchronization Errors
Memory Synchronization Ordering
CS446/646 C. Papachristos
Synchronization Errors
Memory Synchronization Ordering
CS446/646 C. Papachristos
Synchronization Errors
Memory Synchronization Ordering
Relaxation of Program Order :
Relaxation of Atomicity :
C. Papachristos
Synchronization Errors
Memory Synchronization Ordering
Back to our problem…
Architectures offer Memory Ordering Instructions
Can be emitted with known tools (Extended Assembly):
Previous Broken Example:
“Double Checked Locking”
if (!init_flag) {
lock();
if (!init_flag) {
my_data = ...;
init_flag = true;
}
unlock();
}
tmp = my_data;
Possible
reordering
Even this can happen (speculatively)
Remember: The “memory” clobber that�forms a Memory Barrier for the Compiler
CS446/646 C. Papachristos
Synchronization Errors
Memory Synchronization Ordering
Back to our problem…
C (/C++) offers portable Atomics
Previous Broken Example:
“Double Checked Locking”
if (!init_flag) {
lock();
if (!init_flag) {
my_data = ...;
init_flag = true;
}
unlock();
}
tmp = my_data;
Possible
reordering
Even this can happen (speculatively)
CS446/646 C. Papachristos
Synchronization Errors
Memory Synchronization Ordering
Back to our problem…
Fixing Double-Checked Locking with explicit Memory Barriers
CS446/646 C. Papachristos
atomic_bool init_flag;
…
bool flag = atomic_load_explicit(&init_flag, memory_order_relaxed);
atomic_thread_fence(memory_order_acquire);
if (!flag) { // replaces: if(!init_flag)
pthread_mutex_lock(&mut);
f = atomic_load_explicit(&init_flag, memory_order_relaxed);
if (!flag) { // replaces: if(!init_flag)
my_data = ...; // some expensive data initialization
atomic_thread_fence(memory_order_release);
atomic_store_explicit(&init_flag, true , memory_order_relaxed); // replaces: init_flag=true;
}
pthread_mutex_unlock(&mut);
}
tmp = my_data;
Guarantees that Read/Write Operations before a Memory Barrier cannot be reordered with Write Operations after the Memory Barrier
Guarantees that Read/Write Operations after a Memory Barrier cannot be reordered with Read Operations before the Memory Barrier
Synchronization Errors
Memory Synchronization Ordering
Back to our problem…
Fixing Double-Checked Locking with explicit Memory Barriers
CS446/646 C. Papachristos
atomic_bool init_flag;
…
bool flag = atomic_load_explicit(&init_flag, memory_order_relaxed);
atomic_thread_fence(memory_order_acquire);
if (!flag) { // replaces: if(!init_flag)
pthread_mutex_lock(&mut);
f = atomic_load_explicit(&init_flag, memory_order_relaxed);
if (!flag) { // replaces: if(!init_flag)
my_data = ...; // some expensive data initialization
atomic_thread_fence(memory_order_release);
atomic_store_explicit(&init_flag, true , memory_order_relaxed); // replaces: init_flag=true;
}
pthread_mutex_unlock(&mut);
}
tmp = my_data;
Why not just a bool (its Read/Write accesses should be Atomic, right) ?
Note:� volatile could work…
Synchronization Exercises
Reader(s)-Writer(s)
Remember: Allow exclusively either a single Writer or multiple Readers
CS446/646 C. Papachristos
// number of readers
int readcount = 0;
// mutual exclusion to readcount
mutex_t mutex;
// exclusive writer or reader (binary sem)
semaphore_t w_or_r(1);
void writer() {
wait(&w_or_r); // lock out readers
// write()
post(&w_or_r); // up for grabs
}
void reader() {
lock(&mutex); // lock readcount
readcount += 1; // have one more reader
if (readcount == 1) // NOT(!): >= 1
wait(&w_or_r); // synch w/ writers
unlock(&mutex); // unlock readcount
// read()
lock(&mutex); // lock readcount
readcount -= 1; // have one less reader
if (readcount == 0)
post(&w_or_r); // up for grabs
unlock(&mutex); // unlock readcount
}
Synchronization Exercises
CS446/646 C. Papachristos
Synchronization Exercises
Bounded-Buffer
Remember: Ring Buffer with Empty / Full limitations
CS446/646 C. Papachristos
// mutex to shared buffer internal properties (e.g. head, tail)
mutex_t mutex;
// count of empty slots
semaphore_t empty(N);
// count of filled-up slots
semaphore_t filled(0);
void producer() {
while (1) {
// produce_new_resource()
wait(&empty); // wait for 1 empty slot
lock(&mutex); // lock buffer list
// insert_resource_to_buffer()
unlock(&mutex); // unlock buffer list
post(&filled); // notify +1 filled slot
}
}
void consumer() {
while (1) {
wait(&filled); // wait for 1 filled slot
lock(&mutex); // lock buffer list
// extract_resource_from_buffer()
unlock(&mutex); // unlock buffer list
post(&empty); // notify +1 empty slot
// consume_resource()
}
}
Synchronization Exercises
Bounded-Buffer
Why is mutex required?
Where are the Critical Sections?
What conditions lead to a Deadlock?
What happens if operations on mutex and filled/empty are switched around?
CS446/646 C. Papachristos
while (1) {
lock(&mutex);
wait(&filled);
If we Block here (due to filled=0 slots available), the mutex is also Locked, and will remain, because no Producer will be able to proceed into its Critical Section to produce & post() (increment) filled.
Synchronization Exercises
H2O Problem
Form water out of two Hydrogen Threads and one Oxygen Thread (H2O)
Key variables:
CS446/646 C. Papachristos
Synchronization Exercises
H2O
Problem
CS446/646 C. Papachristos
int numH = 0; // (global) number of H threads waiting
int numO = 0; // (global) number of O threads waiting
mutex_t mutex; // mutual exclusion
List<thread_status *> waitingH; // H threads waiting queue
List<thread_status *> waitingO; // O threads waiting queue
void HArrives() {
lock(&mutex);
numH++;
if (numH == 2 && numO >= 1) {
h = waitingH.pop();
o = waitingO.pop();
h->ready = true;
o->ready = true;
cond_signal(&h->cv);
cond_signal(&o->cv);
numH -= 2;
numO -= 1;
// make_water()
}
else {
h = new thread_status;
waitingH.push(h);
while (!h->ready)
cond_wait(&h->cv, &mutex); // releases mutex
delete h;
}
unlock(&mutex);
}
Synchronization Exercises
H2O
Problem
CS446/646 C. Papachristos
int numH = 0; // (global) number of H threads waiting
int numO = 0; // (global) number of O threads waiting
mutex_t mutex; // mutual exclusion
List<thread_status *> waitingH; // H threads waiting queue
List<thread_status *> waitingO; // O threads waiting queue
void OArrives() {
lock(&mutex);
numO++;
if (numH >= 2) {
h1 = waitingH.pop();
h2 = waitingH.pop();
h1->ready = true;
h2->ready = true;
cond_signal(&h1->cv);
cond_signal(&h2->cv);
numH -= 2;
numO -= 1;
// make_water()
}
else {
o = new thread_status;
waitingO.push(o);
while (!o->ready)
cond_wait(&o->cv, &mutex); // releases mutex
delete o;
}
unlock(&mutex);
}
Time for Questions !
CS-446/646
CS446/646 C. Papachristos