Brief
Concurrency Problems
- Atomicity (data)
- Arises when: concurrent updates to shared data
- Ordering (control)
- $T_2$ wait until $T_1$ has executed $x$
Atomicity
classic example:
balance++=>load+add+storeanother example: list insert (shared data structure)
node_t *head = NULL; typedef struct node { int value; node_t *next; } node_t; void list_insert(int v) { // malloc is thread safe (multiple threads can call it w/o locking) node_t *tmp = malloc(sizeof(node_t)); assert(tmp != NULL); tmp->value = v; // Critical section tmp->next = head; head = tmp; }T1 T2 list_insert(3)list_insert(5)head: (10)->…tmp: (3)tmp: (5)tmp: (3)->(10)tmp: (5)->(10)head: (3)->(10)head: (5)->(10)undeterministic
lock section:
- Too large: need to make sure to unlock if error
Lock Implementation
- Q1) How to implement a lock?
- key: need h/w support (more powerful instruction)
- later, also need OS support
- Q2) What makes a good lock?
- Properties: fairness, overhead, correctness
Spin Lock
typedef struct mutex {
int lock; // 0 -> "free", 1 -> "locked"
} mutex_t;
// This is a single instruction in x86
int AtomicExchange(int *lock, int val) {
int old = *lock;
*lock = val;
return old;
}
void init(mutex_t *m) { m->lock = 0; }
void lock(mutex_t *m) {
while (AtomicExchange(&m->lock, 1) == 1) {
// busy wait
}
}
void unlock(mutex_t *m) { m->lock = 0; }
- Correct
- Overhead -> under high contention, lots of CPU cycles wasted
- Fairness -> could spin for “long time”
Ticket Lock
typedef struct lock {
int ticket;
int turn;
} lock_t;
// This is a single instruction
int AtomicFetchAndAdd(int *ptr) {
int old = *ptr;
*ptr = old + 1;
return old;
}
void init(lock_t *lock) {
lock->ticket = 0;
lock->turn = 0;
}
void lock(lock_t *lock) {
int myturn = AtomicFetchAndAdd(&lock->ticket);
while (lock->turn != myturn) {
// Busy wait
}
}
void unlock(lock_t *lock) { lock->turn += 1; }
- Fair
- Performance - spinning on each thread’s own variable