Concurrency and Modules
Shared Counter Lock
A Lock protects shared data so only one thread updates it at a time. Replay
shows the two worker threads taking turns in one source pane.
lock-protected mutation
Use a lock around a read-modify-write operation when multiple threads share the same object.
Counter Updates
shared_counter_lock.py
Replay: real traced execution (multi-file project)
import threading
counter = {"value": 0}
lock = threading.Lock()
def add(delta, repeats):
for _ in range(repeats):
with lock:
counter["value"] = counter["value"] + delta
print("value=" + str(counter["value"]))
repeats = 2
small = threading.Thread(target=add, args=(1, repeats))
large = threading.Thread(target=add, args=(10, repeats))
small.start()
large.start()
small.join()
large.join()
print("final=" + str(counter["value"]))
import threading
counter = {"value": 0}
lock = threading.Lock()
def add(delta, repeats):
for _ in range(repeats):
with lock:
counter["value"] = counter["value"] + delta
print("value=" + str(counter["value"]))
repeats = 1
small = threading.Thread(target=add, args=(1, repeats))
large = threading.Thread(target=add, args=(10, repeats))
small.start()
large.start()
small.join()
large.join()
print("final=" + str(counter["value"]))
import threading
counter = {"value": 0}
lock = threading.Lock()
def add(delta, repeats):
for _ in range(repeats):
with lock:
counter["value"] = counter["value"] + delta
print("value=" + str(counter["value"]))
repeats = 3
small = threading.Thread(target=add, args=(1, repeats))
large = threading.Thread(target=add, args=(10, repeats))
small.start()
large.start()
small.join()
large.join()
print("final=" + str(counter["value"]))
counter ← {'value': 0}, lock ← ⟨unlocked _thread.lock A⟩, repeats ← 2
4counter→ {'value': 0} = {"value": 0}5lock→ ⟨unlocked _thread.lock A⟩ = threading<module 'threading' from '/usr/local/lib/python3.12/threading.py'>.Lock()678def add(delta, repeats):9 for _ in range(repeats):10 with lock:11 counter["value"] = counter["value"] + delta12 print("value=" + str(counter["value"]))131415repeats→ 2 = 2 #@repeats=1, 31617small→ <Thread(Thread-1 (add), initial)> = threading<module 'threading' from '/usr/local/lib/python3.12/threading.py'>.Thread(target=add⟨function add B⟩, args=(1, repeats2))18large→ <Thread(Thread-2 (add), initial)> = threading<module 'threading' from '/usr/local/lib/python3.12/threading.py'>.Thread(target=add⟨function add B⟩, args=(10, repeats2))1920small<Thread(Thread-1 (add), initial)>.start()21large.start()def add(delta, repeats):
pass 1 of 28def add(delta1, repeats2):9 for _ in range(repeats):10 with lock:for _ in range(repeats):
pass 1 of 48def add(delta, repeats):9 for _0 in range(repeats2):10 with lock:11 counter["value"] = counter["value"] + deltaAll 4 passes — pass 1 is the card above pass _1 0 2 1 3 0 4 1 counter[”value”] ← 1
pass 1 of 49for _ in range(repeats):10 with lock⟨locked _thread.lock A⟩:11 counter["value"]→ 1 = counter["value"] + delta112 print("value=" + str(counter["value"]1))outputvalue=1All 4 passes — pass 1 is the card above pass deltacounter[”value”]1 1 0 → 1 2 1 1 → 2 3 10 2 → 12 4 10 12 → 22 small ← <Thread(Thread-1 (add), stopped 130744595642048)>
20small→ <Thread(Thread-1 (add), stopped 130744595642048)>.start()21large<Thread(Thread-2 (add), initial)>.start()22small.join()def add(delta, repeats):
pass 2 of 28def add(delta10, repeats2):9 for _ in range(repeats):10 with lock:large ← <Thread(Thread-2 (add), stopped 130744595642048)>
20small.start()21large→ <Thread(Thread-2 (add), stopped 130744595642048)>.start()22small<Thread(Thread-1 (add), stopped 130744595642048)>.join()23large<Thread(Thread-2 (add), stopped 130744595642048)>.join()2425print("final=" + str(counter["value"]22))outputfinal=22
counter ← {'value': 0}, lock ← ⟨unlocked _thread.lock A⟩, repeats ← 1
4counter→ {'value': 0} = {"value": 0}5lock→ ⟨unlocked _thread.lock A⟩ = threading<module 'threading' from '/usr/local/lib/python3.12/threading.py'>.Lock()678def add(delta, repeats):9 for _ in range(repeats):10 with lock:11 counter["value"] = counter["value"] + delta12 print("value=" + str(counter["value"]))131415repeats→ 1 = 11617small→ <Thread(Thread-1 (add), initial)> = threading<module 'threading' from '/usr/local/lib/python3.12/threading.py'>.Thread(target=add⟨function add B⟩, args=(1, repeats1))18large→ <Thread(Thread-2 (add), initial)> = threading<module 'threading' from '/usr/local/lib/python3.12/threading.py'>.Thread(target=add⟨function add B⟩, args=(10, repeats1))1920small<Thread(Thread-1 (add), initial)>.start()21large.start()def add(delta, repeats):
pass 1 of 28def add(delta1, repeats1):9 for _ in range(repeats):10 with lock:for _ in range(repeats):
pass 1 of 28def add(delta, repeats):9 for _0 in range(repeats1):10 with lock:11 counter["value"] = counter["value"] + deltacounter[”value”] ← 1
pass 1 of 29for _ in range(repeats):10 with lock⟨locked _thread.lock A⟩:11 counter["value"]→ 1 = counter["value"] + delta112 print("value=" + str(counter["value"]1))outputvalue=1small ← <Thread(Thread-1 (add), stopped 137168364631744)>
20small→ <Thread(Thread-1 (add), stopped 137168364631744)>.start()21large<Thread(Thread-2 (add), initial)>.start()22small.join()def add(delta, repeats):
pass 2 of 28def add(delta10, repeats1):9 for _ in range(repeats):10 with lock:for _ in range(repeats):
pass 2 of 28def add(delta, repeats):9 for _0 in range(repeats1):10 with lock:11 counter["value"] = counter["value"] + deltacounter[”value”] ← 11
pass 2 of 29for _ in range(repeats):10 with lock⟨locked _thread.lock A⟩:11 counter["value"]→ 11 = counter["value"] + delta1012 print("value=" + str(counter["value"]11))outputvalue=11large ← <Thread(Thread-2 (add), stopped 137168364631744)>
20small.start()21large→ <Thread(Thread-2 (add), stopped 137168364631744)>.start()22small<Thread(Thread-1 (add), stopped 137168364631744)>.join()23large<Thread(Thread-2 (add), stopped 137168364631744)>.join()2425print("final=" + str(counter["value"]11))outputfinal=11
counter ← {'value': 0}, lock ← ⟨unlocked _thread.lock A⟩, repeats ← 3
4counter→ {'value': 0} = {"value": 0}5lock→ ⟨unlocked _thread.lock A⟩ = threading<module 'threading' from '/usr/local/lib/python3.12/threading.py'>.Lock()678def add(delta, repeats):9 for _ in range(repeats):10 with lock:11 counter["value"] = counter["value"] + delta12 print("value=" + str(counter["value"]))131415repeats→ 3 = 31617small→ <Thread(Thread-1 (add), initial)> = threading<module 'threading' from '/usr/local/lib/python3.12/threading.py'>.Thread(target=add⟨function add B⟩, args=(1, repeats3))18large→ <Thread(Thread-2 (add), initial)> = threading<module 'threading' from '/usr/local/lib/python3.12/threading.py'>.Thread(target=add⟨function add B⟩, args=(10, repeats3))1920small<Thread(Thread-1 (add), initial)>.start()21large.start()def add(delta, repeats):
pass 1 of 28def add(delta1, repeats3):9 for _ in range(repeats):10 with lock:for _ in range(repeats):
pass 1 of 68def add(delta, repeats):9 for _0 in range(repeats3):10 with lock:11 counter["value"] = counter["value"] + deltaAll 6 passes — pass 1 is the card above pass _1 0 2 1 3 2 4 0 5 1 6 2 counter[”value”] ← 1
pass 1 of 69for _ in range(repeats):10 with lock⟨locked _thread.lock A⟩:11 counter["value"]→ 1 = counter["value"] + delta112 print("value=" + str(counter["value"]1))outputvalue=1All 6 passes — pass 1 is the card above pass deltacounter[”value”]1 1 0 → 1 2 1 1 → 2 3 1 2 → 3 4 10 3 → 13 5 10 13 → 23 6 10 23 → 33 small ← <Thread(Thread-1 (add), stopped 134927266006720)>
20small→ <Thread(Thread-1 (add), stopped 134927266006720)>.start()21large<Thread(Thread-2 (add), initial)>.start()22small.join()def add(delta, repeats):
pass 2 of 28def add(delta10, repeats3):9 for _ in range(repeats):10 with lock:large ← <Thread(Thread-2 (add), started 134927266006720)>
20small.start()21large→ <Thread(Thread-2 (add), started 134927266006720)>.start()22small<Thread(Thread-1 (add), stopped 134927266006720)>.join()23large<Thread(Thread-2 (add), started 134927266006720)>.join()large ← <Thread(Thread-2 (add), stopped 134927266006720)>
22small.join()23large→ <Thread(Thread-2 (add), stopped 134927266006720)>.join()2425print("final=" + str(counter["value"]33))outputfinal=33