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

repeats
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"]))
  1. 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()
  2. def add(delta, repeats):

    pass 1 of 2
    8def add(delta1, repeats2):9    for _ in range(repeats):10        with lock:
  3. for _ in range(repeats):

    pass 1 of 4
    8def add(delta, repeats):9    for _0 in range(repeats2):10        with lock:11            counter["value"] = counter["value"] + delta
    All 4 passes — pass 1 is the card above
    pass_
    10
    21
    30
    41
  4. counter[”value”] ← 1

    pass 1 of 4
    9for _ in range(repeats):10    with lock⟨locked _thread.lock A⟩:11        counter["value"]→ 1 = counter["value"] + delta112        print("value=" + str(counter["value"]1))
    outputvalue=1
    All 4 passes — pass 1 is the card above
    passdeltacounter[”value”]
    110 1
    211 2
    3102 12
    41012 22
  5. small ← <Thread(Thread-1 (add), stopped 130744595642048)>

    20small→ <Thread(Thread-1 (add), stopped 130744595642048)>.start()21large<Thread(Thread-2 (add), initial)>.start()22small.join()
  6. def add(delta, repeats):

    pass 2 of 2
    8def add(delta10, repeats2):9    for _ in range(repeats):10        with lock:
  7. 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
  1. 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()
  2. def add(delta, repeats):

    pass 1 of 2
    8def add(delta1, repeats1):9    for _ in range(repeats):10        with lock:
  3. for _ in range(repeats):

    pass 1 of 2
    8def add(delta, repeats):9    for _0 in range(repeats1):10        with lock:11            counter["value"] = counter["value"] + delta
  4. counter[”value”] ← 1

    pass 1 of 2
    9for _ in range(repeats):10    with lock⟨locked _thread.lock A⟩:11        counter["value"]→ 1 = counter["value"] + delta112        print("value=" + str(counter["value"]1))
    outputvalue=1
  5. small ← <Thread(Thread-1 (add), stopped 137168364631744)>

    20small→ <Thread(Thread-1 (add), stopped 137168364631744)>.start()21large<Thread(Thread-2 (add), initial)>.start()22small.join()
  6. def add(delta, repeats):

    pass 2 of 2
    8def add(delta10, repeats1):9    for _ in range(repeats):10        with lock:
  7. for _ in range(repeats):

    pass 2 of 2
    8def add(delta, repeats):9    for _0 in range(repeats1):10        with lock:11            counter["value"] = counter["value"] + delta
  8. counter[”value”] ← 11

    pass 2 of 2
    9for _ in range(repeats):10    with lock⟨locked _thread.lock A⟩:11        counter["value"]→ 11 = counter["value"] + delta1012        print("value=" + str(counter["value"]11))
    outputvalue=11
  9. large ← <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
  1. 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()
  2. def add(delta, repeats):

    pass 1 of 2
    8def add(delta1, repeats3):9    for _ in range(repeats):10        with lock:
  3. for _ in range(repeats):

    pass 1 of 6
    8def add(delta, repeats):9    for _0 in range(repeats3):10        with lock:11            counter["value"] = counter["value"] + delta
    All 6 passes — pass 1 is the card above
    pass_
    10
    21
    32
    40
    51
    62
  4. counter[”value”] ← 1

    pass 1 of 6
    9for _ in range(repeats):10    with lock⟨locked _thread.lock A⟩:11        counter["value"]→ 1 = counter["value"] + delta112        print("value=" + str(counter["value"]1))
    outputvalue=1
    All 6 passes — pass 1 is the card above
    passdeltacounter[”value”]
    110 1
    211 2
    312 3
    4103 13
    51013 23
    61023 33
  5. small ← <Thread(Thread-1 (add), stopped 134927266006720)>

    20small→ <Thread(Thread-1 (add), stopped 134927266006720)>.start()21large<Thread(Thread-2 (add), initial)>.start()22small.join()
  6. def add(delta, repeats):

    pass 2 of 2
    8def add(delta10, repeats3):9    for _ in range(repeats):10        with lock:
  7. 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()
  8. 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