Latticework

Command Palette

Search for a command to run...

Concurrency

Threads & Locks

20 min

Explanation

"Increment a counter" looks atomic in source code (counter += 1) but is actually three separate steps at the hardware level: read the current value, compute the new value, write it back. When two threads run this without coordination, their steps can interleave in ways that lose updates — this is a race condition.

def simulate_race(schedule, initial_value=0):
    shared = initial_value
    thread_local = {}
    for thread_id, step in schedule:
        if step == "read":
            thread_local[thread_id] = shared
        elif step == "write":
            shared = thread_local[thread_id] + 1
    return shared
Try it

A lock's entire job is to make the 'BAD' interleaving impossible -- it forces every thread's read+write pair to complete as one uninterrupted unit before another thread's read can start, which is exactly the 'GOOD' schedule above.

Loading editor…
Exercise

Write `simulate_race(schedule, initial_value=0)`: `schedule` is a list of `(thread_id, step)` pairs where `step` is `'read'` (thread caches the current shared value) or `'write'` (thread writes cached-value + 1 back to shared). Simulate the schedule in order and return the final shared value — this models an UNSYNCHRONIZED read-modify-write increment.

Quiz

Two threads both increment a shared counter once, with no lock, starting from 0. What's the WORST-case final value, and why?

Checkpoint

You can simulate an explicit thread interleaving to see exactly how an unsynchronized read-modify-write produces a lost update.