Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lære Async Locks and asyncio.Lock() | Asyncio in Practice
Python Asyncio in Depth

Async Locks and asyncio.Lock()

Sveip for å vise menyen

The asyncio event loop runs in a single thread, so two coroutines never execute at the exact same moment. But they can still interleave – one coroutine can be interrupted at any await point, and another can modify shared state before the first resumes. asyncio.Lock() prevents this.

The Problem: Interleaved State Mutation

Without a lock, shared state can become inconsistent when multiple coroutines modify it concurrently:

12345678910111213141516171819202122232425
import asyncio import nest_asyncio nest_asyncio.apply() shared_counter = 0 # Incrementing a shared counter without a lock async def increment(label): global shared_counter current_value = shared_counter await asyncio.sleep(0) # Yielding control – another coroutine can run here shared_counter = current_value + 1 print(f"{label}: counter = {shared_counter}") async def main(): await asyncio.gather( increment("task_A"), increment("task_B"), increment("task_C"), ) print(f"Final counter: {shared_counter}") # Expected 3, may get 1 asyncio.run(main())

await asyncio.sleep(0) yields control without actually sleeping. At that point, another coroutine reads the same stale value and overwrites it.

Using asyncio.Lock()

A lock ensures only one coroutine can execute the protected block at a time:

123456789101112131415161718192021222324252627
import asyncio import nest_asyncio nest_asyncio.apply() shared_counter = 0 lock = asyncio.Lock() # Incrementing a shared counter safely with a lock async def increment(label): global shared_counter async with lock: # Acquiring the lock current_value = shared_counter await asyncio.sleep(0) # Safe – no other coroutine can enter the lock shared_counter = current_value + 1 print(f"{label}: counter = {shared_counter}") async def main(): await asyncio.gather( increment("task_A"), increment("task_B"), increment("task_C"), ) print(f"Final counter: {shared_counter}") # Always 3 asyncio.run(main())

The async with lock block is entered by only one coroutine at a time. Any other coroutine that tries to acquire the lock waits until it is released.

A Realistic Example: Shared Cache

Locks are commonly used to protect shared data structures like caches:

1234567891011121314151617181920212223242526272829303132333435363738
import asyncio import httpx import nest_asyncio nest_asyncio.apply() cache = {} cache_lock = asyncio.Lock() # Fetching a post with a shared cache protected by a lock async def fetch_post_cached(client, post_id): async with cache_lock: if post_id in cache: print(f"Cache hit for post {post_id}") return cache[post_id] response = await client.get( f"https://jsonplaceholder.typicode.com/posts/{post_id}" ) title = response.json()["title"] async with cache_lock: cache[post_id] = title return title async def main(): async with httpx.AsyncClient() as client: results = await asyncio.gather( fetch_post_cached(client, 1), fetch_post_cached(client, 1), # Second request hits cache fetch_post_cached(client, 2), ) for result in results: print(result) asyncio.run(main())

When to Use Locks

Locks are needed when:

  • Multiple coroutines read and then write the same variable;
  • A check-then-act sequence must be atomic (e.g., cache lookup then insert);
  • A shared data structure (list, dict) is modified by more than one coroutine.

Locks are not needed for read-only access to shared data – concurrent reads are always safe.

question mark

When is asyncio.Lock() necessary?

Velg det helt riktige svaret

Alt var klart?

Hvordan kan vi forbedre det?

Takk for tilbakemeldingene dine!

Seksjon 3. Kapittel 2

Spør AI

expand

Spør AI

ChatGPT

Spør om hva du vil, eller prøv ett av de foreslåtte spørsmålene for å starte chatten vår

Seksjon 3. Kapittel 2
some-alt