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

Async Locks and asyncio.Lock()

メニューを表示するにはスワイプしてください

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?

正しい答えを選んでください

すべて明確でしたか?

どのように改善できますか?

フィードバックありがとうございます!

セクション 3.  2

AIに質問する

expand

AIに質問する

ChatGPT

何でも質問するか、提案された質問の1つを試してチャットを始めてください

セクション 3.  2
some-alt