chapter 28 locks chien-chung shen cis, ud [email protected]

16
Chapter 28 Locks Chien-Chung Shen CIS, UD [email protected]

Upload: damon-cummings

Post on 29-Jan-2016

224 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

Chapter 28Locks

Chien-Chung ShenCIS, UD

[email protected]

Page 2: 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

• lock_t mutex; // some globally-allocated lock ’mutex’ … lock(&mutex); balance = balance + 1; // critlcal section unlock(&mutex);

Page 3: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

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

Page 4: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

Evaluating Locks

• Three criteria– Mutual exclusion - correctness– Fairness – avoid starvation– Performance - overhead

Page 5: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

Controlling Interrupts

void lock () { disableInterrupt(); }

void unlock() { enable Interrupt(); }

Negatives:–Allow calling thread to perform privileged operation–Does not work on multiprocessors

Page 6: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

First Attempt – use a variable

typedef struct __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 (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?

Page 7: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

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

Page 8: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

Test-and-Set

• Semantics int TestAndSet(int *ptr, int new) { int old = *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

Page 9: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

Spin Lock with Test-and-Set

typedef struct __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)

Page 10: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

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

Page 11: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

Compare-and-Swap

• On x86 - compare-and-exchange cmpxchgl• Semanticsint CompareAndSwap(int *ptr, int expected, int new) { int actual = *ptr; if (actual == expected) *ptr = new; return actual;}

• Lockvoid lock(lock_t *lock) { while (CompareAndSwap(&lock->flag, 0, 1) == 1) ; // spin }

• More powerful than Test-and-Set

Page 12: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

Ticket Lock with Fetch&Addint FetchAndAdd(int *ptr) { // semantics int old = *ptr; *ptr = old + 1; return old; }

typedef struct __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) { int myturn = 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

Page 13: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

How to Avoid Spinning ?

• Need OS support, in addition to hardwarevoid 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

Page 14: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

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

Page 15: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

Queue and Yield/Wakeuptypedef struct __lock_t { int flag; int guard; 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; }

Page 16: Chapter 28 Locks Chien-Chung Shen CIS, UD cshen@cis.udel.edu

Questionsvoid 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 queuex: 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