Software Systems Advanced Synchronization Emery Berger and Mark
Software Systems Advanced Synchronization Emery Berger and Mark Corner University of Massachusetts Amherst UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science
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 UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 2
Synch. Operations § Safety: – Locks provide mutual exclusion § Coordination: – Condition variables provide ordering UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 3
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 UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 4
Safety: Example n “The too much milk problem” n Model of need to synchronize activities UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 5
Why You Need Locks thread A thread B if (no milk && no note) leave note buy milk remove note n too much milk Does this work? UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 6
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); UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 7
Too Much Milk: Locks thread A thread B lock(&l) if (no milk) buy milk unlock(&l) UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 8
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 UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science
Exercise! § Simple multi-threaded program – N = number of iterations – Spawn that many threads to compute • value = expensive. Computation(i) – Add value (safely!) to total § Use: – pthread_mutex_init, _lock, _unlock • pthread_mutex_t my. Lock; – pthread_create, pthread_join • pthread_t threads[N]; UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 10
Prototypes pthread_t these. Arethreads[100]; pthread_mutex_t this. Is. ALock; typedef void * fn. Type (void *); pthread_create (pthread_t *, NULL, fn. Type, 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 *) UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 11
Solution #include <pthread. h> int total = 0; pthread_mutex_t lock; void * wrapper (void * x) { int v = *((int *) x); delete ((int *) x); int res = exp. Comp (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 * new. I = new int; *new. I = i; pthread_create (&threads[i], NULL, wrapper, (void *) new. I); } // join for (int i = 0; i<n; i++) { pthread_join (threads[i], NULL); } // done printf (“total = %dn”, total); return 0; } UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 12
Synch. Operations § Safety: – Locks provide mutual exclusion § Coordination: – Condition variables provide ordering UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 13
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 UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 14
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(); } V = remove(); unlock(); UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science
Solution: Sleep! § Sleep = – “don’t run me until something happens” § What about this? Dequeue(){ lock(); if (queue empty) { sleep(); } take one item; unlock(); } Enqueue(){ lock(); insert item; if (thread waiting) wake up dequeuer(); unlock(); } § Cannot hold lock while sleeping! UNIVERSITY OF MASSACHUSETTS AMHERST • 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 UNIVERSITY OF MASSACHUSETTS AMHERST • 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 UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science
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 UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science 19
Condition Variable Exercise § Implement “Producer Consumer” – One thread enqueues, another dequeues void * consumer (void *){ void * producer(void *){ while (true) { pthread_mutex_lock(&l); while (q. empty()){ q. push_front (1); pthread_cond_wait(&nempty, &l); pthread_cond_signal(&nempty); } pthread_mutex_unlock(&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? UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science
Bounded-Buffer Exercise § Implement “Producer Consumer” – One thread enqueues, another dequeues void * consumer (void *){ void * producer(void *){ while (true) { pthread_mutex_lock(&l); while (q. empty()){ while (q. size() == N) { pthread_cond_wait(&nempty, &l); pthread_cond_wait(&nfull, &l); } } cout << q. pop_back() << endl; q. push_front (1); pthread_cond_signal(&nfull); pthread_cond_signal(&nempty); pthread_mutex_unlock(&l); } } UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science
- Slides: 21