A race condition is a concurrency bug in which the outcome depends on which concurrent operation finishes first. Shared data can therefore end in an unexpected or inconsistent state. section 6.5.1 section 1.5
The usual trigger is a non-atomic operation that looks like one action but contains several steps. Incrementing a counter, for example, reads its value, adds one, and writes the result; two workers can both read zero and overwrite each other with one. section 6.5.1 section 11.1
Two processes incrementing one counter
Two processes each increment a shared integer and the program expects two. Repeated runs sometimes produce one, demonstrating nondeterministic behavior caused by overlapping reads and writes. section 6.5.1
Recognize the pattern across Python concurrency models:
- Threads can race when they modify shared, non-thread-safe data because execution order is uncontrolled. section 7.3
- Processes can race when they use explicitly shared memory, even though ordinary process memory is separate. section 7.3 section 6.5.1
- Asyncio avoids races from ordinary non-atomic operations while one coroutine runs continuously, but an await can allow another task to observe or change shared state before the operation finishes. section 11.1
A single-threaded asyncio race
An increment coroutine copies the counter to a temporary variable, awaits briefly, and writes the temporary value back. Many tasks can consequently lose updates even though only one thread executes Python code at a time. section 11.1
Listing 11.2 A single-threaded race condition
import asyncio
counter: int = 0
async def increment():
global counter
temp_counter = counter
temp_counter = temp_counter + 1
await asyncio.sleep(0.01)
counter = temp_counter
async def main():
global counter
for _ in range(1000):
tasks = [asyncio.create_task(increment()) for _ in range(100)]
await asyncio.gather(*tasks)
print(f'Counter is {counter}')
assert counter == 100
counter = 0
asyncio.run(main())
The runnable asyncio example makes the suspension point and lost updates visible in a compact counter test. section 11.1
Protect a critical section with a lock so only one worker changes shared state at a time. Threaded code uses threading.Lock; multiprocessing and asyncio use their corresponding synchronization primitives. Keep the protected region focused, since locks can also contribute to deadlocks. section 7.3 section 6.7
Counting completed requests
A thread-pool program makes many blocking web requests and increments a shared completion counter inside a threading.Lock context, allowing a reporter task to display progress safely. section 7.3



















