Challenge 2: A Race Condition, Then Fixed — Possible Solution ==================================================================== import threading # --- broken version, no lock --- counter = 0 def increment_unsafe(): global counter for _ in range(50_000): counter += 1 threads = [threading.Thread(target=increment_unsafe) for _ in range(2)] for t in threads: t.start() for t in threads: t.join() print(f"Unsafe result: {counter}") # likely something LESS than 100000 # --- fixed version, with a lock --- counter = 0 lock = threading.Lock() def increment_safe(): global counter for _ in range(50_000): with lock: counter += 1 threads = [threading.Thread(target=increment_safe) for _ in range(2)] for t in threads: t.start() for t in threads: t.join() print(f"Safe result: {counter}") # always exactly 100000 Output (unsafe count will vary run to run, safe count will not): Unsafe result: 92841 Safe result: 100000 WHY THIS WORKS AS AN ANSWER ------------------------------ increment_unsafe() reuses global (Course 1, Chapter 8) to modify the module-level counter variable from inside a function, incrementing it 50,000 times per thread with the plain counter += 1 this chapter's warn-box specifically calls out as NOT atomic — reading the current value, adding 1, and writing it back are three separate steps, and two threads can interleave between those steps, each occasionally overwriting the other's update rather than building on it. Running two threads of 50,000 increments each should total 100,000, but the printed unsafe result usually comes in lower, because some increments get silently lost to this exact interleaving. increment_safe() reuses the chapter's own with lock: pattern exactly — wrapping counter += 1 in a with lock: block (tying directly back to Chapter 2's context-manager coverage) ensures only one thread can be inside that block at any moment, forcing the read-add-write sequence to complete fully before the other thread's turn. This eliminates the interleaving that caused lost updates in the unsafe version. Both versions use the exact same threading.Thread/.start()/.join() structure from this chapter's own download() example — the ONLY difference between the broken and fixed versions is the presence of the lock, isolating exactly what the lock fixes and demonstrating that 100,000 is the only correct final count once the race condition is eliminated.