160 likes | 329 Views
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
E N D
Chapter 28Locks 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 • lock_tmutex; // some globally-allocated lock ’mutex’ … lock(&mutex); balance = balance + 1; // critlcal section unlock(&mutex);
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
Evaluating Locks • Three criteria • Mutual exclusion - correctness • Fairness – avoid starvation • Performance - overhead
Controlling Interrupts void lock () { disableInterrupt(); } void unlock() { enable Interrupt(); } Negatives: • Allow calling thread to perform privileged operation • Does not work on multiprocessors
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?
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
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
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)
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
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
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
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
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
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; }
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