Race Conditions, and Mutexes¶
Mother of All Race conditions: Integer Increment¶
import threading
integer = 0
ntimes = 10000000
def increment_background():
global integer, ntimes
for _ in range(ntimes):
integer += 1
t = threading.Thread(target=increment_background,
daemon=True) # <----- mark as daemon
t.start()
for _ in range(ntimes):
integer += 1
print('final value:', integer)
$ python code/race-condition.py
final value: 14784035
⟶ Load Modify Store Conflict
Load Modify Store Conflict¶
CPU A |
CPU B |
Memory |
||
Instr |
Loc |
Instr |
Loc |
Glob |
load |
42 |
42 |
||
42 |
load |
42 |
42 |
|
inc |
43 |
42 |
||
43 |
inc |
43 |
42 |
|
43 |
store |
43 |
43 |
|
store |
43 |
43 |
43 |
Solution: Mutex (Explicit Acquire/Release)¶
Mutex (for “MUTual EXclusion”), short:
Lock
Best analogy: toilet door lock
import threading
integer = 0
ntimes = 10000000
lock = threading.Lock()
def increment_background():
global integer, ntimes
for _ in range(ntimes):
lock.acquire()
integer += 1
lock.release()
t = threading.Thread(target=increment_background,
daemon=True) # <----- mark as daemon
t.start()
for _ in range(ntimes):
lock.acquire()
integer += 1
lock.release()
print('final value:', integer)
$ python code/mutex.py
final value: 20000000
Solution: Mutex (with
Statement)¶
Calling
lock.acquire()
andlock.release()
manually⟶ what if exception is raised in the middle? (Well, integer increments don’t usually raise one; you get the point though.)
⟶ This is what the with
statement is there fore
(threading.Lock
can act as a context manager)
import threading
integer = 0
ntimes = 10000000
lock = threading.Lock()
def increment_background():
global integer, ntimes
for _ in range(ntimes):
with lock:
integer += 1
t = threading.Thread(target=increment_background,
daemon=True) # <----- mark as daemon
t.start()
for _ in range(ntimes):
with lock:
integer += 1
print('final value:', integer)