Why Does Python Need asyncio.Lock?
INTRODUCTION After understanding , I thought I had learned everything required to control multiple coroutines.
A semaphore limits how many coroutines can execute simultaneously.
Then another question came to my mind.
If Python's event loop executes only one coroutine at a time, why do we even need a Lock?
Initially, I assumed a lock was unnecessary because there was only one thread.
But after experimenting with shared variables, I realized that even though only one coroutine executes at a particular instant, multiple coroutines can still interfere with each other.
In this article, I'll explain the problem that led to , how it works, and why almost every backend application uses it.
What You Will Learn Why exists What is a race condition What is a critical section How Lock works internally Practical examples Real-world backend use cases Prerequisites Before learning , you should understand: Coroutines Event Loop await asyncio.Semaphore The Problem Suppose we have a shared variable.
Now imagine two coroutines trying to increment it.
Initially I expected the final value to become because two coroutines are incrementing the counter.
But that wasn't what happened.
Let's See What Actually Happens Initially Now Coroutine A starts executing.
The coroutine reaches .
The event loop suspends it and starts another coroutine.
Now Coroutine B executes.
Notice something interesting.
Both coroutines have already read Now Coroutine A resumes.
Then Coroutine B resumes.
The final value becomes instead of This is called a Race Condition.
Why Did This Happen?
Initially I blamed the Event Loop.
Later I realized, the Event Loop didn't do anything wrong.
Its job is simply to switch between coroutines whenever they reach an .
The real problem was that both coroutines were modifying the same data.
The Critical Section The block of code that accesses or modifies shared data is called the Critical Section.
If multiple coroutines execute this block, the shared data can become inconsistent.
So this block should only be executed by one coroutine at a time.
Python's Solution Python introduced A Lock ensures that only one coroutine can execute the critical section at a time.
If another coroutine tries to enter, it simply waits until the lock is released.
How Lock Works Imagine only one key exists.
Unlike Semaphore, a Lock always allows only one coroutine inside.
Practical Example Output Now both coroutines execute safely because only one coroutine can enter the critical section.
Why async with?
Initially I wondered why we write instead of The answer became clear when I introduced an exception.
The lock is never released.
Every other coroutine waits forever.
Using Python automatically releases the lock, even if an exception occurs.
What if Lock Didn't Exist?
Imagine an online banking application.
Current Balance User A User B Without a Lock, both operations may read the same balance before updating it.
The final balance becomes incorrect.
The same problem occurs in Inventory systems Payment gateways Order processing Shared counters Database updates Real-world Use Cases Banking Systems Updating account balances safely.
Inventory Management Preventing two customers from purchasing the last available product.
Order Processing Generating unique order numbers.
Shared Cache Updating shared cache values safely.
Advantages of Lock Prevents race conditions Protects shared resources Maintains data consistency Automatically releases resources with Makes concurrent applications reliable Conclusion Initially I thought that because uses only one thread, race conditions couldn't happen.
But after understanding how the event loop switches between coroutines at every , I realized that multiple coroutines can still interfere with shared data. doesn't make the program asynchronous.
It simply makes shared resources safe by ensuring that only one coroutine executes the critical section at a time.
Once I understood the problem it solves, using a L