UNIVERSITY OF NIVERSITY OF MASSACHUSETTS ASSACHUSETTS AMHERST • MHERST • Department of Computer Science Department of Computer Science Software Systems Advanced Synchronization Emery Berger and Mark Corner University of Massachusetts Amherst
Jan 15, 2016
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science
Software SystemsAdvanced Synchronization
Emery Berger and Mark CornerUniversity of Massachusetts
Amherst
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 2
Why Synchronization? Synchronization serves two purposes:
– Ensure safety for shared updates• Avoid race conditions
– Coordinate actions of threads• Parallel computation• Event notification
ALL interleavings must be correct– there are lots of interleavings of events– also constrain as little as possible
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 3
Synch. Operations Safety:
– Locks provide mutual exclusion Coordination:
– Condition variables provide ordering
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 4
Safety Multiple threads/processes
– access shared resource simultaneously Safe only if:
– All accesses have no effect on resource,e.g., reading a variable, or
– All accesses idempotent• E.g., a = abs(x), a = highbit(a)
– Only one access at a time: mutual exclusion
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 5
“The too much milk problem”
Model of need to synchronize activities
Safety: Example
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 6
thread A
if (no milk && no note)
leave note
buy milk
remove note
thread B
if (no milk && no note)
leave note
buy milk
remove note
Does this work?too much milk
Why You Need Locks
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 7
Mutual Exclusion Prevent more than one thread from
accessing critical section– Serializes access to section
Lock, update, unlock:– lock (&l);– update data; /* critical section */– unlock (&l);
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 8
thread A
lock(&l)
if (no milk)
buy milk
unlock(&l)
thread B
lock(&l)
if (no milk)
buy milk
unlock(&l)
Too Much Milk: Locks
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science
What data is shared? Some data is shared
– code, data, heap Each thread has private data
– Stack, SP, PC All access to shared data must be safe
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 10
Exercise! Simple multi-threaded program
– N = number of iterations– Spawn that many threads to compute
• value = expensiveComputation(i)– Add value (safely!) to total
Use:– pthread_mutex_init, _lock, _unlock
• pthread_mutex_t myLock;– pthread_create, pthread_join
• pthread_t threads[N];
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science
pthread_t theseArethreads[100];pthread_mutex_t thisIsALock;typedef void * fnType (void *);pthread_create (pthread_t *, NULL, fnType, void *);pthread_join (pthread_t, void *);pthread_mutex_init (pthread_mutex_t *, NULL)pthread_mutex_lock (pthread_mutex_t *)pthread_mutex_unlock (pthread_mutex_t *)
11
Prototypes
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 12
#include <pthread.h>int total = 0;pthread_mutex_t lock;void * wrapper (void * x) { int v = *((int *) x); delete ((int *) x); int res = expComp (v); pthread_mutex_lock (&lock); total += res; pthread_mutex_unlock (&lock); return NULL;}
int main (int argc, char * argv[]) { int n = atoi(argv[1]); // mutex init pthread_mutex_init (&lock); // allocate threads pthread_t * threads = new pthread_t[n]; // spawn threads for (int i = 0; i<n; i++) { // heap allocate args int * newI = new int; *newI = i; pthread_create (&threads[i], NULL,
wrapper, (void *) newI); } // join for (int i = 0; i<n; i++) { pthread_join (threads[i], NULL); } // done printf (“total = %d\n”, total); return 0;}
Solution
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 13
Synch. Operations Safety:
– Locks provide mutual exclusion Coordination:
– Condition variables provide ordering
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 14
Synch Problem: Queue Suppose we have a thread-safe queue
– insert(item), remove(), empty()– must protect access with locks
Options for remove when queue empty:– Return special error value (e.g., NULL)– Throw an exception– Wait for something to appear in the queue
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science
Three Possible Solutions Spin
– Works?
Could release lock– Works?
Re acquire Lock
lock();while(empty()) {}unlock();v = remove();
unlock(); while(empty()) {}lock();v = remove();
lock()while (empty()) {
unlock();lock();
}V = remove();unlock();
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science
Solution: Sleep! Sleep =
– “don’t run me until something happens” What about this?
Cannot hold lock while sleeping!
Dequeue(){ lock(); if (queue empty) { sleep(); }
take one item; unlock();}
Enqueue(){ lock(); insert item; if (thread waiting) wake up dequeuer(); unlock();}
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science
Quick Exercise Does this work?
Dequeue(){ lock(); if (queue empty){ unlock(); sleep(); remove item; } else unlock;}
Enqueue(){lock(); insert item;if (thread waiting) wake up
dequeuer();unlock();
}
No! – deq releases lock, then enq looks for sleeping thread
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science
Condition Variables Make it possible/easy to go to sleep
– Atomically:• release lock• put thread on wait queue• go to sleep
Each cv has a queue of waiting threads Worry about threads that have been put on the
wait queue but have NOT gone to sleep yet?– no, because those two actions are atomic
Each condition variable associated with one lock
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science 19
Condition Variables Wait for 1 event, atomically release lock
– wait(Lock& l, CV& c)• If queue is empty, wait
– Atomically releases lock, goes to sleep– You must be holding lock!– May reacquire lock when awakened (pthreads do)
– signal(CV& c)• Insert item in queue
– Wakes up one waiting thread, if any
– broadcast(CV& c)• Wakes up all waiting threads
Monitors = locks + condition variables– Sometimes combined with data structures
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science
Condition Variable Exercise Implement “Producer Consumer”
– One thread enqueues, another dequeuesvoid * consumer (void *){ while (true) { pthread_mutex_lock(&l); while (q.empty()){ pthread_cond_wait(&nempty, &l); } cout << q.pop_back() << endl; pthread_mutex_unlock(&l); }}
Two Questions?– Can I use if instead of while (to check cond)?– Can I signal after unlock?
void * producer(void *){ while (true) { pthread_mutex_lock(&l); q.push_front (1); pthread_cond_signal(&nempty); pthread_mutex_unlock(&l); }}
UUNIVERSITY OF NIVERSITY OF MMASSACHUSETTS ASSACHUSETTS AAMHERST • MHERST • Department of Computer Science Department of Computer Science
Bounded-Buffer Exercise Implement “Producer Consumer”
– One thread enqueues, another dequeuesvoid * consumer (void *){ while (true) { pthread_mutex_lock(&l); while (q.empty()){ pthread_cond_wait(&nempty, &l); } cout << q.pop_back() << endl; pthread_cond_signal(&nfull); pthread_mutex_unlock(&l); }}
void * producer(void *){ while (true) { pthread_mutex_lock(&l); while (q.size() == N) { pthread_cond_wait(&nfull,&l); } q.push_front (1); pthread_cond_signal(&nempty); pthread_mutex_unlock(&l); }}