Brief
Concurrency #
- Atomicity (Data)
- OS Support + hardware support
- Ordering (Control - When code runs)
- New primitive: condition variable
- What makes a good lock?
- Correctness
- Overhead
- Fairness (extreme: starvation - never get lock)
- Problems: (Focus on overhead)
- Spin lock (
xchg-based)- T1 enter critical section, T2 spin
- How to reduce waste of spinning
OS Support
yield() System Call #
- when thread calls
yield- moves calling thread from
RUNNING to READY - (Push to back of scheduling queue)
while (AtomicExchange(&m->lock, 1) == 1) {
yield();
}
- Worst case w/o
yield- $waste = num;of;threads \times length;of;time;slice$
- $T_1$ in middle of critical section -> timer interrupt -> switch to $T_2$ spin -> $T_3$ spin …
- With
yield- $waste = num;of;threads \times context;switch;cost$
- Worst case with
yield is much faster than just spinning
Using Queue with Spin-lock #
typedef struct __lock_t {
int flag; // state of lock: 1=held, 0=free
int guard; // use to protect flag, queue
queue_t *q; // explicit queue of waiters
} lock_t;
void lock_init(lock_t *lock) {
lock->flag = lock->guard = 0;
lock->q = queue_init();
}
void lock(lock_t *lock) {
while (xchg(&lock->guard, 1) == 1)
; // spin
if (lock->flag == 0) { // lock is free: grab it!
lock->flag = 1;
lock->guard = 0;
} else { // lock not free: sleep
queue_push(lock->q, gettid());
lock->guard = 0;
park(); // put self to sleep (from RUNNING to BLOCKED)
}
}
void unlock(lock_t *lock) {
while (xchg(&lock->guard, 1) == 1)
; // spin
if (queue_empty(lock->q))
lock->flag = 0;
else
unpark(queue_pop(lock->q));
lock->guard = 0;
}
- Race condition
- set guard to 0 before park (bad)
setpark();
lock->guard = 0;
park();
Ordering Problem
Condition Variables #