Locks, Events, and Semaphores
Python · Concurrency · Run with python3
Explanation
`threading.Lock` is a mutual exclusion lock — only one thread can hold it at a time. `RLock` (reentrant lock) can be acquired multiple times by the same thread. `threading.Event` coordinates threads: `event.wait()` blocks until `event.set()` is called. `threading.Semaphore(n)` limits concurrent access to `n` threads. `asyncio` mirrors all these: `asyncio.Lock`, `asyncio.Event`, `asyncio.Semaphore` — same semantics but `await`-based.
Real-World Scenario
A rate-limited API client uses `asyncio.Semaphore(10)` to allow at most 10 simultaneous requests. Without it, launching 1,000 concurrent requests would overwhelm the API and trigger 429 responses. ```python import asyncio async def rate_limited_fetch(sem: asyncio.Semaphore, url: str) -> str: async with sem: # At most 3 concurrent requests await asyncio.sleep(0.1) # simulate network I/O return f"OK: {url}" async def main() -> None: sem = asyncio.Semaphore(3) # max 3 concurrent urls = [f"https://api.example.com/{i}" for i in range(10)] results = await asyncio.gather(*[rate_limited_fetch(sem, u) for u in urls]) print(f"Completed {len(results)} requests with concurrency limit 3") asyncio.run(main()) # threading.Event example import threading ready = threading.Event() def worker(): ready.wait() # Block until main signals print("Worker: starting work") t = threading.Thread(target=worker) t.start() print("Main: initializing...") import time; time.sleep(0.1) ready.set() # Unblock the worker t.join() ```
Quick Snippet
import asyncio
async def rate_limited_fetch(sem: asyncio.Semaphore, url: str) -> str:
async with sem: # At most 3 concurrent requests
await asyncio.sleep(0.1) # simulate network I/O
return f"OK: {url}"
async def main() -> None:
sem = asyncio.Semaphore(3) # max 3 concurrent
urls = [f"https://api.example.com/{i}" for i in range(10)]
results = await asyncio.gather(*[rate_limited_fetch(sem, u) for u in urls])
print(f"Completed {len(results)} requests with concurrency limit 3")
asyncio.run(main())
# threading.Event example
import threading
ready = threading.Event()
def worker():
ready.wait() # Block until main signals
print("Worker: starting work")
t = threading.Thread(target=worker)
t.start()
print("Main: initializing...")
import time; time.sleep(0.1)
ready.set() # Unblock the worker
t.join()Full Example
# Compile: python3 locks_events.py
# Output:
# Completed 10 requests with concurrency limit 3
# Worker: starting work
import asyncio
import threading
import time
async def rate_limited_fetch(sem: asyncio.Semaphore, url: str) -> str:
async with sem:
await asyncio.sleep(0.01)
return f"OK: {url}"
async def async_main() -> None:
sem = asyncio.Semaphore(3)
urls = [f"https://api.example.com/{i}" for i in range(10)]
results = await asyncio.gather(*[rate_limited_fetch(sem, u) for u in urls])
print(f"Completed {len(results)} requests with concurrency limit 3")
asyncio.run(async_main())
# threading.Event
ready = threading.Event()
def worker():
ready.wait()
print("Worker: starting work")
t = threading.Thread(target=worker)
t.start()
time.sleep(0.05)
ready.set()
t.join()
# threading.Lock
lock = threading.Lock()
shared = 0
def increment(n):
global shared
for _ in range(n):
with lock:
shared += 1
threads = [threading.Thread(target=increment, args=(100,)) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Shared counter: {shared}") # Always 500
python3 locks_events.py