Locks
Locks Basics
- difficult to reason about multithreaded code that updates shared states
- race conditions: scheduling decisions can produce different results
- would be a lot easier if we have a mechanism to claim exclusive access to shared states
- in our example, much easier if
global_x++
is done as an inseparable (atomic) operation
- compare and swap (CAS)
- arguments:
loc, old_val, new_val
- atomic instruction to conditionally update memory location
loc
- if
*loc
has the value of old_val
, updates it to new_val
, returns true
- if
*loc
does not the value of old_val
, returns false
- great but only provides atomicity to updates to one memory location
- single core strategy: disable interrupts
- if we can't be interrupted, we have exclusive access
- provide atomicity for doing any number of operations
- not generalizable
- disabling interrupt is a privileged operation, can't be used by processes (why?)
- is a per-core operation, does not provide exclusive access when there are multiple cores
- locks
- an abstraction that guarantees exclusive access (mutual exclusion) to a designated section code (critical section)
- properties of a lock
- safety: nothing bad ever happens
- only one thread in the critical section at a time
- liveness: something good eventually happens
- progress: a thread can enter the critical section if no one's there
- fairness: be fair
- bounded wait: there's an upperbound to the wait, can't keep skipping over a thread
- may still be pre-empted in the middle of a critical section, so why is this better?
- lock APIs
lock_acquire
: acquires a lock, does not return until lock is acquired
lock_release
: releases a lock, available for other threads to acquire
- locking granularity
- how much data is the lock protecting?
- for an array of struct, a lock for the entire array? or a lock for each entry?
- to protect shared kernel data, a lock for the whole kernel? a lock for each data structure? a lock for specific fields of a struct?
- coarse-grained vs fine-grained locking
- why does it matter?
- need to think about who and how they may access the data
Types of Lock
- spinlock
- when the lock is not free, keep checking the lock status in a while loop until it's free
- is this a good use of CPU?
- what happens if the critical section takes a long time?
- what if there are many threads trying to acquire the lock?
- how might we implement a spinlock? hint: atomic instruction
- sleeplock/mutex
- when the lock is not free, blocks/sleeps until it's free
- is this a good use of CPU?
- what is the cost of blocking?
- what happens if the critical section is small?
- sometimes you can't block! e.g. inside an interrupt handler
- how might we implement a sleeplock? what information do we need to track?