1 / 16

Chapter 28 Locks

Chapter 28 Locks. Chien -Chung Shen CIS, UD cshen@cis.udel.edu. Basic Ideas. Problem on concurrent programming – like to execute a sequence of instructions atomically on single CPU with interrupts Solution: put locks around critical sections

graceland
Download Presentation

Chapter 28 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. Chapter 28Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

  2. Basic Ideas • Problem on concurrent programming– like to execute a sequence of instructions atomically on single CPU with interrupts • Solution: put locks around critical sections • lock_tmutex; // some globally-allocated lock ’mutex’ … lock(&mutex); balance = balance + 1; // critlcal section unlock(&mutex);

  3. Pthread Locks • pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; Pthread_mutex_lock(&lock); // wrapper for pthread_mutex_lock() balance = balance + 1;Pthread_mutex_unlock(&lock); • Course-grained locking vs. fine-grained locking • “Long” vs. “short” critical sections • example with file access

  4. Evaluating Locks • Three criteria • Mutual exclusion - correctness • Fairness – avoid starvation • Performance - overhead

  5. Controlling Interrupts void lock () { disableInterrupt(); } void unlock() { enable Interrupt(); } Negatives: • Allow calling thread to perform privileged operation • Does not work on multiprocessors

  6. First Attempt – use a variable typedefstruct __lock_t { intflag; } lock_t; void init(lock_t *mutex) {mutex->flag = 0; // 0 -> lock is available, 1 -> held } 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; } What problems does this solution have?

  7. Code Interleaving Thread 1 Thread 2 flag == 0 call lock()while (flag == 1)interrupt: switch to Thread 2 call lock() while (flag == 1) flag = 1;interrupt: switch to Thread 1 flag = 1; // set flag to 1 (too!) • Problems: • correctness – no guarantee of mutual exclusion • Performance – spin-waiting

  8. Test-and-Set • Semantics intTestAndSet(int *ptr, int new) { intold = *ptr; // fetch old value at ptr *ptr = new; // store ’new’ into ptr return old; // return the old value } • Returns the old value pointed to by ptr, and simultaneously updates said value to new • Make “test” (of old lock value) and “set” (of new value) a single atomic operation • SPARC – ldstub// load/store unsigned byte • x86 – xchg// atomic exchange

  9. Spin Lock with Test-and-Set typedefstruct __lock_t { int flag; } lock_t; void init(lock_t *mutex) {mutex->flag = 0; // 0 -> lock is available, 1 -> held } void lock(lock_t *mutex) { while (TestAndSet(&lock->flag, 1) == 1) // TEST the flag ; // spin-wait (do nothing) } void unlock(lock_t *mutex) { mutex->flag = 0; } • As long as the lock is held by another thread, TestAndSet() will repeatedly return 1, and thus the calling thread will spin-wait • What kind of scheduler do we need on single processor? • preemptive scheduler (interrupt threads via timer)

  10. Evaluation of Spin Lock • Three criteria • Mutual exclusion – correctness • yes • Fairness – avoid starvation • no • Performance – overhead • Bad on single CPU • Reasonably well on multiple CPUs, assuming critical sections are short

  11. Compare-and-Swap • On x86 - compare-and-exchange cmpxchgl • Semantics intCompareAndSwap(int *ptr, int expected, int new) { intactual = *ptr; if (actual == expected) *ptr = new; return actual; } • Lock void lock(lock_t *lock) { while (CompareAndSwap(&lock->flag, 0, 1) == 1) ; // spin } • More powerful than Test-and-Set

  12. Ticket Lock with Fetch&Add intFetchAndAdd(int *ptr) { // semantics intold = *ptr; *ptr = old + 1; return old; } typedefstruct __lock_t { int ticket; 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); // get a ticket while (lock->turn != myturn) ; // spin if not my turn } void unlock(lock_t *lock) { FetchAndAdd(&lock->turn); // enable the next waiting thread } • Anything good ? • ensure progress for all threads and fair

  13. How to Avoid Spinning ? • Need OS support, in addition to hardware void init() {flag = 0; } void lock() { while (TestAndSet(&flag, 1) == 1) // TEST the flag yield();// give up CPU and move to READY state } void unlock() { flag = 0; } • Another overhead ? (think 100 threads) • context switching overhead • Still one problem not solved • starvation

  14. Sleeping Instead of Spinning • Explicitly exert some control over who gets to acquire the lock next after the current holder releases it • What “data structure” would you use? • queue • park() – put calling thread to sleep • unpark()– wake up a thread

  15. Queue and Yield/Wakeup typedefstruct __lock_t { intflag; intguard; queue_t*q; } lock_t; void lock_init(lock_t *m) { m->flag = 0; m->guard = 0; queue_init(m->q); } 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()); // added to the lock’s queue m->guard = 0; park(); // put calling thread to sleep } } 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 unpark(queue_remove(m->q)); // hold lock (for next thread!) m->guard = 0; }

  16. Questions 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()); // added to the lock’s queue x: m->guard = 0; y: park(); // put calling thread to sleep } } • Why is guard used? • Can x and y be swapped? 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 unpark(queue_remove(m->q)); // hold lock (for next thread!) m->guard = 0; } • Why flag does not get set to 0 when another thread gets woken up? • the waking thread does not hold the guard

More Related