Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Python asyncio Semaphore: bound active work, not queued tasks

Last updated: 1 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

An asyncio semaphore limits simultaneous entries into a critical section; it does not bound the number of waiting tasks.

Download Python source kit

Operation contract

A delivery-status client permits only two active requests to a downstream service. The semaphore holds that limit, but all four scheduled tasks still exist and can retain their inputs while waiting. For an unbounded feed, admit jobs through a bounded queue before creating tasks. Task groups own the child lifetimes; they do not impose admission limits by themselves.

Failure and ownership boundary

Cancellation inside async with releases a permit as the context exits. A coroutine that catches CancelledError for cleanup should normally propagate it after cleanup; suppressing it can confuse structured concurrency. A semaphore does not turn a blocking library call into cooperative I/O. The program deliberately uses sleep(0) to yield while the limit is held, making the observed peak repeatable.

Working program

python
import asyncio

async def main():
    gate = asyncio.Semaphore(2)
    active = 0
    peak = 0

    async def fetch_status(parcel_id):
        nonlocal active, peak
        async with gate:
            active += 1
            peak = max(peak, active)
            await asyncio.sleep(0)
            active -= 1
            return parcel_id

    async with asyncio.TaskGroup() as group:
        tasks = [group.create_task(fetch_status(parcel_id))
                 for parcel_id in ("P-47", "P-48", "P-49", "P-50")]
    print("scheduled:", len(tasks))
    print("peak:", peak)
    print("results:", [task.result() for task in tasks])

asyncio.run(main())

Output

Output
scheduled: 4
peak: 2
results: ['P-47', 'P-48', 'P-49', 'P-50']

Costs and limits

The permit count is bounded, but task memory grows with admitted inputs. A fixed-capacity queue is required to bound waiting payloads.

Common Mistakes

  • A semaphore does not cap the number of created tasks.
  • Do not swallow cancellation and claim the request stopped.
  • Async syntax does not make synchronous blocking I/O cooperative.

Connected lessons

Python asyncio.Queue: backpressure and completion accounting, Python asyncio TaskGroup: cancel sibling work and retain failure evidence, Python asyncio timeout: cancellation and cleanup ownership.

Test this contract in the Python concurrency and storage boundary quiz.

python
async-semaphore-admission
Storage details