Fundamentals 8 min read

Using asyncio Synchronization Primitives in Python: Event, Lock, Semaphore, Condition, and Queue

This article explains how to use Python's asyncio library to synchronize and communicate between coroutines, covering Event, Lock, Semaphore, Condition, and Queue primitives with clear code examples demonstrating each mechanism in practice.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Using asyncio Synchronization Primitives in Python: Event, Lock, Semaphore, Condition, and Queue

Python's asyncio library provides several synchronization primitives that allow coroutines to coordinate their execution and share data safely. This guide introduces the most common primitives—Event, Lock, Semaphore, Condition, and Queue—and shows how to apply them with practical code samples.

Event is a simple signaling mechanism where one coroutine sets the event and others wait for it. The following example demonstrates a coroutine waiting for an event and another setting it after a delay.

import asyncio
async def wait_for_event(event):
    print("Waiting for event...")
    await event.wait()
    print("Event received!")
async def set_event(event):
    print("Setting event...")
    await asyncio.sleep(2)
    event.set()
async def main():
    event = asyncio.Event()
    task1 = asyncio.create_task(wait_for_event(event))
    task2 = asyncio.create_task(set_event(event))
    await asyncio.gather(task1, task2)
asyncio.run(main())

Lock provides mutual exclusion, ensuring that only one coroutine can access a shared resource at a time. The example below creates a lock and runs several coroutines that acquire it sequentially.

import asyncio
async def acquire_lock(lock):
    print("Trying to acquire lock...")
    async with lock:
        print("Lock acquired!")
        await asyncio.sleep(2)
        print("Lock released!")
async def main():
    lock = asyncio.Lock()
    tasks = []
    for _ in range(3):
        task = asyncio.create_task(acquire_lock(lock))
        tasks.append(task)
    await asyncio.gather(*tasks)
asyncio.run(main())

Semaphore limits the number of coroutines that can run a particular section of code concurrently. The sample creates a semaphore with a limit of two and launches three tasks that must acquire it before proceeding.

import asyncio
async def access_resource(semaphore):
    async with semaphore:
        print("Accessing resource...")
        await asyncio.sleep(2)
        print("Resource access complete!")
async def main():
    semaphore = asyncio.Semaphore(2)
    tasks = []
    for _ in range(3):
        task = asyncio.create_task(access_resource(semaphore))
        tasks.append(task)
    await asyncio.gather(*tasks)
asyncio.run(main())

Condition is a more flexible primitive that allows coroutines to wait until a specific condition is met and then be notified. The code below shows one coroutine waiting on a condition and another notifying all waiting coroutines after a delay.

import asyncio
async def wait_for_condition(condition):
    print("Waiting for condition...")
    async with condition:
        await condition.wait()
        print("Condition satisfied!")
async def set_condition(condition):
    print("Setting condition...")
    await asyncio.sleep(2)
    async with condition:
        condition.notify_all()
async def main():
    condition = asyncio.Condition()
    task1 = asyncio.create_task(wait_for_condition(condition))
    task2 = asyncio.create_task(set_condition(condition))
    await asyncio.gather(task1, task2)
asyncio.run(main())

Combined example demonstrates how multiple primitives can be used together in a single program. It creates an Event , a Condition , and a Lock , then coordinates three workers that wait for the event, the condition, and the lock respectively.

import asyncio
async def wait_for_event(event):
    print("Worker 1 waiting for event...")
    await event.wait()
    print("Worker 1 event received!")
async def wait_for_condition(condition):
    print("Worker 2 waiting for condition...")
    async with condition:
        await condition.wait()
        print("Worker 2 condition satisfied!")
async def acquire_lock(lock):
    print("Worker 3 trying to acquire lock...")
    async with lock:
        print("Worker 3 lock acquired!")
        await asyncio.sleep(2)
        print("Worker 3 lock released!")
async def main():
    event = asyncio.Event()
    condition = asyncio.Condition()
    lock = asyncio.Lock()
    task1 = asyncio.create_task(wait_for_event(event))
    task2 = asyncio.create_task(wait_for_condition(condition))
    task3 = asyncio.create_task(acquire_lock(lock))
    await asyncio.sleep(1)
    event.set()
    await asyncio.sleep(1)
    async with condition:
        condition.notify_all()
    await asyncio.gather(task1, task2, task3)
asyncio.run(main())

Queue enables producer‑consumer patterns by allowing coroutines to put items into a queue and other coroutines to retrieve them. The following example shows a producer generating items and a consumer processing them until the queue is exhausted.

import asyncio
async def produce(queue):
    for i in range(5):
        print(f"Producing item {i}")
        await queue.put(i)
        await asyncio.sleep(1)
async def consume(queue):
    while True:
        item = await queue.get()
        print(f"Consuming item {item}")
        queue.task_done()
        if item is None:
            break
        await asyncio.sleep(1)
async def main():
    queue = asyncio.Queue()
    producer_task = asyncio.create_task(produce(queue))
    consumer_task = asyncio.create_task(consume(queue))
    await producer_task
    await queue.join()
    consumer_task.cancel()
asyncio.run(main())

In summary, the asyncio module offers a rich set of synchronization tools—Event, Lock, Semaphore, Condition, and Queue—that help developers write clear, efficient, and safe asynchronous Python code.

SynchronizationeventSemaphoreLockQueueasyncioCondition
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.