A Counter Race Despite the GIL

import threading

counter = 0

def increment():
    global counter
    for _ in range(1_000_000):
        counter += 1

threads = [threading.Thread(target=increment) for _ in range(4)]
for t in threads:
    t.start()
for t in threads:
    t.join()

print(counter)

Four threads each increment the same global counter one million times. In CPython, is print(counter) guaranteed to print 4000000?

A

Yes, because counter is declared global inside increment, and the GIL treats every access to a global variable as its own atomic critical section separate from local variables.

B

No. counter += 1 is not one bytecode step: it loads the current value of counter, adds 1, and stores the result back, as separate instructions. The GIL can switch to another thread between those instructions, so two threads can both load the same old value and one thread's increment can be silently overwritten by the other's. The final count can come out below 4000000, though whether and how often that actually happens on a given run is timing-dependent.

C

Yes, because the GIL guarantees that any single Python statement, including a compound one like +=, executes as one uninterruptible step.

D

No, but only because four threads exceeds some fixed thread limit the GIL enforces; with two threads or fewer the final count would always come out exact.