1 / 35

Lecture 10 Locks

Lecture 10 Locks. Scheduling Control: Mutex /Lock. Basic pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock (&lock); x = x + 1; // or whatever your critical section is pthread_mutex_unlock (&lock); Other variants int pthread_mutex_trylock ( pthread_mutex_t * mutex );

titus
Télécharger la présentation

Lecture 10 Locks

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Lecture 10Locks

  2. Scheduling Control: Mutex/Lock • Basic pthread_mutex_tlock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&lock); x = x + 1; // or whatever your critical section is pthread_mutex_unlock(&lock); • Other variants intpthread_mutex_trylock(pthread_mutex_t *mutex); intpthread_mutex_timedlock(pthread_mutex_t *mutex, structtimespec *abs_timeout);

  3. int main(intargc, char *argv[]) { if (argc != 2) { fprintf(stderr, "usage: ...\n"); exit(1); } max = atoi(argv[1]); pthread_t p1, p2; printf("main: begin [counter = %d] [%x]\n", counter, (unsigned int) &counter); Pthread_create(&p1, NULL, mythread, "A"); Pthread_create(&p2, NULL, mythread, "B"); // join waits for the threads to finish Pthread_join(p1, NULL); Pthread_join(p2, NULL); printf("main: done\n [counter: %d]\n [should: %d]\n", counter, max*2); return 0; } #include <stdio.h> #include "mythreads.h" #include <stdlib.h> #include <pthread.h> int max; // shared global variable volatile int counter = 0; void * mythread(void *arg) { char *letter = arg; inti; // stack printf("%s: begin\n", letter); for (i = 0; i < max; i++) { counter = counter + 1; } printf("%s: done\n", letter); return NULL; }

  4. ControllingInterrupts void lock() { DisableInterrupts(); } void unlock() { EnableInterrupts(); } • Would it work? • Problems: • We have to trust the application • Does not work on multiprocessors • Inefficient • Only used in limited contexts

  5. Evaluating Locks • Correctness • Fairness • Performance

  6. typedefstruct __lock_t { int flag; } lock_t; void init(lock_t *mutex) { // 0 -> lock is available, 1 -> held mutex->flag = 0; } void lock(lock_t *mutex) { while (mutex->flag == 1) // TEST the flag ; // spin-wait (do nothing) mutex->flag = 1; // now SET it! } void unlock(lock_t *mutex) { mutex->flag = 0; }

  7. Test And Set (Atomic Exchange) intTestAndSet(int *ptr, int new) { intold = *ptr; // fetch old value at ptr *ptr = new; // store ’new’ into ptr return old; // return the old value }

  8. typedefstruct __lock_t { int flag; } lock_t; void init(lock_t *mutex) { // 0 -> lock is available, 1 -> held mutex->flag = 0; } void lock(lock_t *mutex) { while (TestAndSet(&lock->flag, 1) == 1) ; // spin-wait (do nothing) } void unlock(lock_t *mutex) { mutex->flag = 0; }

  9. Evaluating Spin Locks • Correctness: yes • Fairness: no • Performance: • bad on single CPU • reasonable if the number of threads roughly equals the number of CPUs

  10. Compare-And-Swap intCompareAndSwap(int *ptr, int expected, int new) { intactual = *ptr; if (actual == expected) *ptr = new; return actual; }

  11. typedefstruct __lock_t { int flag; } lock_t; void init(lock_t *mutex) { // 0 -> lock is available, 1 -> held mutex->flag = 0; } void lock(lock_t *mutex) { while (CompareAndSwap(&lock->flag, 0, 1) == 1) ; // spin-wait (do nothing) } void unlock(lock_t *mutex) { mutex->flag = 0; }

  12. Load-Linked and Store-Conditional intLoadLinked(int *ptr) { return *ptr; } intStoreConditional(int *ptr, int value) { if (no update to*ptr since the LoadLinked to it) { *ptr = value; return 1; // success! } else { return 0; // failed to update } }

  13. void lock(lock_t *mutex) { while (1) { while (LoadLinked(&mutex->flag) == 1) ; // spin until it’s zero if (StoreConditional(&mutex->flag, 1) == 1) return; // if set-it-to-1 succeeded: all done // otherwise: try it all over again } } void unlock(lock_t *mutex) { mutex->flag = 0; }

  14. Fetch-And-Add and Ticket Locks intFetchAndAdd(int *ptr) { intold = *ptr; *ptr = old + 1; return old; }

  15. typedefstruct __lock_t { intticket; int turn; } lock_t; void lock_init(lock_t *lock) { lock->ticket = 0; lock->turn = 0; } void lock(lock_t *lock) { intmyturn = FetchAndAdd(&lock->ticket); while (lock->turn != myturn) ; // spin } void unlock(lock_t *lock) { FetchAndAdd(&lock->turn); }

  16. Spinning is Bad • Imagine two threads on a single processor • Imagine N threads on a single processor

  17. void init() { flag = 0; } void lock() { while (TestAndSet(&flag, 1) == 1) yield(); // give up the CPU } void unlock() { flag = 0; }

  18. Sleeping Instead Of Spinning • On Solaris, OS provides two calls: • park() to put a calling thread to sleep • unpark(threadID) to wake a particular thread as designated by threadID

  19. typedefstruct __lock_t { intflag; int guard; queue_t *q; } lock_t; void lock_init(lock_t *m) { m->flag = 0; m->guard = 0; queue_init(m->q); }

  20. void lock(lock_t *m) { while (TestAndSet(&m->guard, 1) == 1) ; //acquire guard lock by spinning if (m->flag == 0) { m->flag = 1; // lock is acquired m->guard = 0; } else { queue_add(m->q, gettid()); m->guard = 0; park(); } }

  21. void unlock(lock_t *m) { while (TestAndSet(&m->guard, 1) == 1) ; //acquire guard lock by spinning if (queue_empty(m->q)) m->flag = 0; // let go of lock; no one wants it else // hold lock (for next thread!) unpark(queue_remove(m->q)); m->guard = 0; }

  22. void lock(lock_t *m) { while (TestAndSet(&m->guard, 1) == 1) ; //acquire guard lock by spinning if (m->flag == 0) { m->flag = 1; // lock is acquired m->guard = 0; } else { queue_add(m->q, gettid()); setpark(); // new code m->guard = 0; park(); } }

  23. Different Support on Linux • On Solaris, OS provides two calls: • futex_wait(address, expected) puts the calling thread to sleep, assuming the value at address is equal to expected. If it is not equal, the call returns immediately. • futex_wake(address) wakes one thread that is waiting on the queue.

  24. void lock(lock_t *m) { intv; /* Bit 31 was clear, we got the mutex(fastpath) */ if (atomic_bit_test_set(m, 31) == 0) return; atomic_increment(m); while (1) { if (atomic_bit_test_set(m, 31) == 0) { atomic_decrement(m); return; } /* We have to wait now. First make sure the futex value we are monitoring is truly negative (i.e. locked). */ v = *m; if (v >= 0) continue; futex_wait (m, v); } }

  25. void unlock(lock_t *m) { /* Adding 0x80000000 to the counter results in 0 if & only if there are not other interested threads */ if (atomic_add_zero (mutex, 0x80000000)) return; /* There are other threads waiting for this mutex, wake one of them up. */ futex_wake (mutex); }

  26. Concurrent Counters typedefstruct __counter_t { intvalue; } counter_t; void init(counter_t *c) { c->value = 0; } void increment(counter_t *c) { c->value++; } void decrement(counter_t *c) { c->value--; } intget(counter_t *c) { return c->value; }

  27. typedefstruct __counter_t { int global; // global count pthread_mutex_tglock; // global lock int local[NUMCPUS]; // local count (per cpu) pthread_mutex_tllock[NUMCPUS]; // ... and locks int threshold; // update frequency } counter_t; void init(counter_t *c, int threshold) { c->threshold = threshold; c->global = 0; pthread_mutex_init(&c->glock, NULL); inti; for (i = 0; i < NUMCPUS; i++) { c->local[i] = 0; pthread_mutex_init(&c->llock[i], NULL); } }

  28. void update(counter_t *c, intthreadID, intamt) { pthread_mutex_lock(&c->llock[threadID]); c->local[threadID] += amt; // assumes amt > 0 if (c->local[threadID] >= c->threshold) { pthread_mutex_lock(&c->glock); c->global += c->local[threadID]; pthread_mutex_unlock(&c->glock); c->local[threadID] = 0; } pthread_mutex_unlock(&c->llock[threadID]); } intget(counter_t *c) { pthread_mutex_lock(&c->glock); intval = c->global; pthread_mutex_unlock(&c->glock); return val; // only approximate! }

  29. Concurrent Linked Lists typedefstruct __node_t { intkey; struct __node_t *next; } node_t; typedefstruct __list_t { node_t *head; pthread_mutex_t lock; } list_t; void List_Init(list_t *L) { L->head = NULL; pthread_mutex_init(&L->lock, NULL); }

  30. intList_Insert(list_t *L, int key) { pthread_mutex_lock(&L->lock); node_t *new = malloc(sizeof(node_t)); if (new == NULL) { perror("malloc"); pthread_mutex_unlock(&L->lock); return -1; // fail } new->key = key; new->next = L->head; L->head = new; pthread_mutex_unlock(&L->lock); return 0; // success }

  31. intList_Lookup(list_t *L, int key) { pthread_mutex_lock(&L->lock); node_t *curr = L->head; while (curr) { if (curr->key == key) { pthread_mutex_unlock(&L->lock); return 0; // success } curr = curr->next; } pthread_mutex_unlock(&L->lock); return -1; // failure }

  32. intList_Insert(list_t *L, int key) { // synchronization not needed node_t *new = malloc(sizeof(node_t)); if (new == NULL) { perror("malloc"); return; } new->key = key; // just lock critical section pthread_mutex_lock(&L->lock); new->next = L->head; L->head = new; pthread_mutex_unlock(&L->lock); }

  33. intList_Lookup(list_t *L, int key) { intrv = -1; pthread_mutex_lock(&L->lock); node_t *curr = L->head; while (curr) { if (curr->key == key) { rv = 0; break; } curr = curr->next; } pthread_mutex_unlock(&L->lock); return rv; // now both success and failure }

  34. Hand-over-hand Locking

  35. Next: Condition Variables Semaphores

More Related