Lecture 13 Concurrency Bugs CV rules of thumb
Lecture 13 Concurrency Bugs
CV rules of thumb: • Keep state in addition to CV’s • Always do wait/signal with lock held • Whenever you acquire a lock, recheck state
Implementing Join with CV void thread_exit() { Mutex_lock(&m); done = 1; Cond_signal(&c); Mutex_unlock(&m); } void thread_join() { Mutex_lock(&m); if (done == 0) Cond_wait(&c, &m); Mutex_unlock(&m); } // a // b // // w x y z
Producer/Consumer Problem with CV void *producer(void *arg) { for (int i=0; i < loops; i++){ } } Mutex_lock(&m); //p 1 while (numfull == max) //p 2 Cond_wait(&E, &m); //p 3 put(i); //p 4 Cond_signal(&F); //p 5 Mutex_unlock(&m); //p 6 void *consumer(void *arg) while (1) { Mutex_lock(&m); while (numfull == 0) Cond_wait(&F, &m); int tmp = get(); Cond_signal(&E); Mutex_unlock(&m); printf("%dn", tmp); } } { //c 1 //c 2 //c 3 //c 4 //c 5 //c 6
Semaphore • For the following init’s, what might the use be? • sem_init(&s, 0); • sem_init(&s, 1); • sem_init(&s, N);
P/C problem semaphore int main(int argc, char *argv[]) { //. . . sem_init(&empty, MAX); sem_init(&full, 0); sem_t empty, full, mutex; void *consumer(void *arg) { int i, tmp = 0; while (1) { sem_wait(&full); sem_init(&mutex, 1); //. . . } void *producer(void *arg) { int i; // C 1 for (i = 0; i < loops; i++) { sem_wait(&mutex); // C 1. 5 tmp = get(); sem_wait(&empty); // P 1 // C 2 sem_wait(&mutex); // P 1. 5 sem_post(&mutex); // C 2. 5 put(i); sem_post(&empty); // C 3 sem_post(&mutex); // P 2. 5 printf("%dn", tmp); sem_post(&full); } } // P 2 } } // P 3
Build semaphores with locks and CV’s typedef struct __sem_t { int value; cond_t cond; lock_t lock; } sem_t; void sem_init(sem_t *s, int value) { s->value = value; Cond_init(&s->cond); Lock_init(&s->lock); } void sem_wait(sem_t *s) {…} void sem_post(sem_t *s) {…}
Concurrency bugs in history
Race • A data race occurs when: two or more threads in a single process access the same memory location concurrently, and. at least one of the accesses is for writing, and. the threads are not using any exclusive locks to control their accesses to that memory. • A race condition is an undesirable situation that occurs when a device or system attempts to perform two or more operations at the same time, but because of the nature of the device or system, the operations must be done in the proper sequence in order to be done correctly.
Concurrency Bugs are Common and Various Application Atomicity Order Deadlock other My. SQL 12 1 9 1 Apache 7 6 4 0 Mozilla 29 15 16 0 Open. Office 3 2 2 1
Atomicity: My. SQL Thread 1: Thread 2: pthread_mutex_lock(&lock); if (thd->proc_info) { pthread_mutex_lock(&lock); … thd->proc_info = NULL; fputs(thd->proc_info, …); pthread_mutex_unlock(&lock); … } pthread_mutex_unlock(&lock);
Ordering: Mozilla Thread 1: Thread 2: void init() { void m. Main(…) { … … m. Thread Mutex_lock(&mt. Lock); = PR_Create. Thread(m. Main, …); while(mt. Init == 0) pthread_mutex_lock(&mt. Lock); Cond_wait(&mt. Cond, &mt. Lock); mt. Init = 1; Mutex_unlock(&mt. Lock); pthread_cond_signal(&mt. Cond); m. State = m. Thread->State; pthread_mutex_unlock(&mt. Lock); … … } }
Race: My. SQL Thread 1: Thread 2: if (thd->proc_info) { … fputs(thd->proc_info, …); … } thd->proc_info = NULL;
Race: Mozilla Thread 1: Thread 2: void init() { void m. Main(…) { … … m. Thread m. State = m. Thread->State; = PR_Create. Thread(m. Main, …); … }
Data Race Free DOES not mean Concurrency Bug Free
Deadlock
Boring Code Example Thread 1 lock(&A); lock(&B); Thread 2 lock(&B); lock(&A);
What’s Wrong? set_t *set_union (set_t *s 1, set_t *s 2) { set_t *rv = Malloc(sizeof(*rv)); Mutex_lock(&s 1 ->lock); Mutex_lock(&s 2 ->lock); for(int i=0; i<s 1 ->len; i++) { if(set_contains(s 2, s 1 ->items[i]) set_add(rv, s 1 ->items[i]); Mutex_unlock(&s 2 ->lock); Mutex_unlock(&s 1 ->lock); }
Encapsulation • Modularity can make it harder to see deadlocks. Thread 1 Thread 2 rv = set_union(set. A, set. B); rv = set_union(set. B, set. A);
Deadlock Theory • Deadlocks can only happen with these four conditions: • • mutual exclusion hold-and-wait no preemption circular wait • Eliminate deadlock by eliminating one condition.
Mutual Exclusion • Def: Threads claim exclusive control of resources that they require (e. g. , thread grabs a lock).
Wait-Free Algorithms • Strategy: eliminate lock use. • Assume we have: • int Comp. And. Swap(int *addr, int expected, int new) • 0: fail, 1: success void add_v 1(int *val, int amt) { Mutex_lock(&m); *val += amt; Mutex_unlock(&m); } void add_v 2(int *val, int amt) { do { int old = *value; } while(!Comp. And. Swap(val, old+amt); }
Wait-Free Insert void insert(int val) { node_t *n = Malloc(sizeof(*n)); n->val = val; lock(&m); do { n->next = head; head = n; } while (!Comp. And. Swap(&head, unlock(&m); } n->next, n)); }
Deadlock Theory • Deadlocks can only happen with these four conditions: • • mutual exclusion hold-and-wait no preemption circular wait • Eliminate deadlock by eliminating one condition.
Hold-and-Wait • Def: Threads hold resources allocated to them (e. g. , locks they have already acquired) while waiting for additional resources (e. g. , locks they wish to acquire).
Eliminate Hold-and-Wait • Strategy: acquire all locks atomically once (cannot acquire again until all have been released). • For this, use a meta lock, like this: lock(&meta); lock(&L 1); lock(&L 2); … unlock(&meta); • disadvantages?
Deadlock Theory • Deadlocks can only happen with these four conditions: • • mutual exclusion hold-and-wait no preemption circular wait • Eliminate deadlock by eliminating one condition.
No preemption • Def: Resources (e. g. , locks) cannot be forcibly removed from threads that are holding them
Support Preemption • Strategy: if we can’t get what we want, release what we have. top: lock(A); if (trylock(B) == -1) { unlock(A); goto top; } …
Deadlock Theory • Deadlocks can only happen with these four conditions: • • mutual exclusion hold-and-wait no preemption circular wait • Eliminate deadlock by eliminating one condition.
Circular Wait • Def: There exists a circular chain of threads such that each thread holds a resource (e. g. , lock) being requested by next thread in the chain.
Eliminating Circular Wait • Strategy: • decide which locks should be acquired before others • if A before B, never acquire A if B is already held! • document this, and write code accordingly
Other approaches • Deadlock avoidance vis scheduling • Detect and recover
- Slides: 33