13 Locks
13 Locks
Locks
Reading materials: Chap.28
1
Pusan National University
2
Pusan National University
3
Pusan National University
extern int g;
void inc() movl 0x1000, %eax
{ addl $1, %eax
g++; movl %eax, 0x1000
}
Thread T1 Thread T2
Sharing Resources
■ Local variables are not shared among threads
▪ Refer to data on the stack
▪ Each thread has its own stack
▪ Never pass/share/store a pointer to a local variable on another thread’s
stack
■ Global variables are shared among threads
▪ Stored in static data segment, accessible by any thread
■ Dynamic objects are shared among threads
▪ Stored in the heap, shared through the pointers
■ Also, processes can share memory (shmem)
5
Pusan National University
Synchronization Problem
■ Concurrency leads to non-deterministic results
▪ Two or more concurrent threads accessing a shared resource create a
race condition
▪ The output of the program is not deterministic; it varies from run to run
even with same inputs, depending on timing
▪ Hard to debug (“Heisenbugs”)
6
Pusan National University
7
Pusan National University
Critical Section
■ A critical section is a piece of code that accesses a shared
resource, usually a variable or data structure
8
Pusan National University
9
Pusan National University
10
Pusan National University
▪ Other threads are prevented from entering the critical section while the
first thread that holds the lock is in there.
■ unlock()
▪ Once the owner of the lock calls unlock(), the lock is now available
(free) again.
▪ Wake up any thread waiting in lock()
11
Pusan National University
Using locks
■ Lock is initially free
■ Call lock() before entering a critical section, and unlock()
after leaving it
■ lock() does not return until the caller holds the lock
■ On unlock(), a thread can spin (spinlock) or block (mutex)
■ At most one thread can hold a lock at a time
12
Pusan National University
Using Locks
13
Pusan National University
■ Fairness
▪ Each thread gets a fair chance at acquiring the lock
■ Performance
▪ Time overhead for a lock without and with contentions (possibly on
multiple CPUs)?
14
Pusan National University
Implementing Locks
■ Controlling interrupts
■ Software-only algorithms
▪ Dekker’s algorithm (1962)
▪ Peterson’s algorithm (1981)
▪ Lamport’s Bakery algorithm for more than two processes (1974)
15
Pusan National University
Controlling Interrupts
■ Disable Interrupts for critical sections
▪ One of the earliest solutions used to provide mutual exclusion
▪ Invented for single-processor systems.
▪ Disabling interrupts blocks external events that could trigger a context
switch (e.g. timer)
▪ The code inside the critical section will not be interrupted
▪ There is no state associated with the lock
1 void lock() {
2 DisableInterrupts();
3 }
4 void unlock() {
5 EnableInterrupts();
6 }
16
Pusan National University
Controlling Interrupts
■ Disable Interrupts for critical sections
▪ Simple
▪ Useful for a single-processor system
▪ Problem:
▪ Require too much trust in applications
– Greedy (or malicious) program could monopolize the processor.
▪ Do not work on multiprocessors
▪ Code that masks or unmasks interrupts be executed slowly by
modern CPUs
17
Pusan National University
Software-only Algorithm
■ The second attempt to implement spinlocks
▪ Note: each load and store instruction is atomic
int flag[2];
void init() {
flag[0] = flag[1] = 0; // 1->thread wants to grab lock
}
void lock() {
int other = 1 – self;
flag[self] = 1; // self: thread ID of caller
while (flag[other] == 1); // spin-wait
}
void unlock() {
flag[self] = 0; // simply undo your intent
}
18
Pusan National University
Peterson’s Algorithm
■ Solves the critical section problem for two processes
int flag[2];
int turn;
void init() {
flag[0] = flag[1] = 0; // 1->thread wants to grab lock
turn = 0; // whose turn? (thread 0 or 1?)
}
void lock() {
int other = 1 – self;
flag[self] = 1; // self: thread ID of caller
turn = other; // make it other thread’s turn
while ((flag[other] == 1) && (turn == other)); // spin-wait
}
void unlock() {
flag[self] = 0; // simply undo your intent
}
19
Pusan National University
20
Pusan National University
21
Pusan National University
22
Pusan National University
23
Pusan National University
■ Fairness: no
▪ Spin locks don’t provide any fairness guarantees.
▪ Indeed, a thread spinning may spin forever.
■ Performance:
▪ In the single CPU, performance overheads can be quire painful.
▪ If the number of threads roughly equals the number of CPUs, spin locks
work reasonably well.
24
Pusan National University
Compare-And-Swap
■ Test whether the value at the address(ptr) is equal to
expected.
▪ If so, update the memory location pointed to by ptr with the new
value.
▪ In either case, return the actual value at that memory location.
1 int CompareAndSwap(int *ptr, int expected, int new) {
2 int actual = *ptr;
3 if (actual == expected)
4 *ptr = new;
5 return actual;
6 }
Compare-and-Swap hardware atomic instruction (C-style)
26
Pusan National University
27
Pusan National University
Fetch-And-Add
■ Atomically increment a value while returning the old value at
a particular address.
1 int FetchAndAdd(int *ptr) {
2 int old = *ptr;
3 *ptr = old + 1;
4 return old;
5 }
28
Pusan National University
Ticket Lock
■ Ticket lock can be built with fetch-and add.
▪ First get a ticket and wait until its turn
▪ Ensure progress for all threads. → fairness
1 typedef struct __lock_t {
2 int ticket;
3 int turn;
4 } lock_t;
5
6 void lock_init(lock_t *lock) {
7 lock->ticket = 0;
8 lock->turn = 0;
9 }
10
11 void lock(lock_t *lock) {
12 int myturn = FetchAndAdd(&lock->ticket);
13 while (lock->turn != myturn)
14 ; // spin
15 }
16 void unlock(lock_t *lock) {
17 FetchAndAdd(&lock->turn);
18 }
29
Pusan National University
So Much Spinning
■ Hardware-based spin locks are simple and they work.
30
Pusan National University
32
Pusan National University
34
Pusan National University
Wakeup/waiting race
■ In case of releasing the lock (thread A) just before the call to
park() (thread B) → Thread B would sleep forever
(potentially).
35
Pusan National University
Futex
■ Linux provides a futex (is similar to Solaris’s park and
unpark).
▪ futex_wait(address, expected)
▪Put the calling thread to sleep
▪ If the value at address is not equal to expected, the call
returns immediately.
▪ futex_wake(address)
▪ Wake one thread that is waiting on the queue.
36
Pusan National University
Futex (Cont.)
■ Snippet from lowlevellock.h in the nptl library
▪ The high bit of the integer v: track whether the lock is held or not
▪ All the other bits : the number of waiters
37
Pusan National University
Futex (Cont.)
16 if (v >= 0)
17 continue;
18 futex_wait(mutex, v);
19 }
20 }
21
22 void mutex_unlock(int *mutex) {
23 /* Adding 0x80000000 to the counter results in 0 if and only if
24 there are not other interested threads */
25 if (atomic_add_zero(mutex, 0x80000000))
26 return;
27 /* There are other threads waiting for this mutex,
28 wake one of them up */
29 futex_wake(mutex);
30 }
38
Pusan National University
Two-Phase Locks
■ A two-phase lock realizes that spinning can be useful if the
lock is about to be released.
▪ First phase
▪The lock spins for a while, hoping that it can acquire the lock.
▪ If the lock is not acquired during the first spin phase, a second
phase is entered,
▪ Second phase
▪ The caller is put to sleep.
▪ The caller is only woken up when the lock becomes free later.
39
Pusan National University
Next Class..
A Parent Waiting For Its Child
1 void *child(void *arg) {
2 printf("child\n");
3 // XXX how to indicate we are done?
4 return NULL;
5 }
6
7 int main(int argc, char *argv[]) {
8 printf("parent: begin\n");
9 pthread_t c;
10 Pthread_create(&c, NULL, child, NULL); // create child
11 // XXX how to wait for child?
12 printf("parent: end\n");
13 return 0;
14 }
40