A semaphore is a synchronization primitive that maintains a counter and permits only a bounded number of execution contexts to access a resource concurrently. Each acquire consumes a permit; each release restores one. When no permits remain, further acquisitions wait. A lock is the special case with a limit of one. section 11.3
Use semaphores when concurrent work must fit within a finite capacity:
- Limit database connections, CPU-intensive work, or requests to an external API. section 11.0 section 11.3
- Wrap an asyncio operation in async with semaphore so acquisition and release are paired automatically. section 11.3
- Choose BoundedSemaphore when manual release logic must detect excess releases; an invalid release raises ValueError. section 11.3.1
Allow two tasks at a time
Create Semaphore(2), start four coroutines with asyncio.gather, and acquire the semaphore inside each coroutine. The first two tasks proceed while the other two wait; completed tasks release permits so the waiting tasks can continue. section 11.3
Listing 11.6 Using semaphores
import asyncio
from asyncio import Semaphore
async def operation(semaphore: Semaphore):
print('Waiting to acquire semaphore...')
async with semaphore:
print('Semaphore acquired!')
await asyncio.sleep(2)
print('Semaphore released!')
async def main():
semaphore = Semaphore(2)
await asyncio.gather(*[operation(semaphore) for _ in range(4)])
asyncio.run(main())
Runnable asyncio code demonstrates four operations sharing two semaphore permits, including acquisition, simulated work, and automatic release. section 11.3




