Locks

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

“The too much milk problem”

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
if (no milk && no note)
leave note
buy milk
remove note

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)
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 = 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];
UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science
10
Prototypes
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 *)
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 = 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;
}
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();
lock();
}
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) {
while (true) {
pthread_mutex_lock(&l);
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) {
while (true) {
pthread_mutex_lock(&l);
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);
pthread_mutex_unlock(&l);
}
}
}
}
UNIVERSITY OF MASSACHUSETTS AMHERST • Department of Computer Science