Semaphores in Python Async Programming Real-World Use Cases
Use semaphore to avoid rate limit from OpenAI While doing LLM-as-a-judge for evaluations, I was facing a rate limit issue from OpenAI. This post will explain how to use Managing shared resources and coordinating concurrent operations can be challenging in the world of asynchronous programming. Enter semaphores: a powerful synchronization primitive that can help you control access to limited resources and coordinate between multiple coroutines. This post will explore practical use cases for semaphores in Python’s asyncio framework, complete with code examples you can adapt for your projects. Before we dive into specific use cases, let’s quickly look at how to use semaphores in Python’s asyncio. The Output: In this example, we create a semaphore that allows up to two workers to access a resource simultaneously. The Let’s look at real-world use cases where semaphores can save the day. Respecting rate limits is crucial when working with external APIs to avoid being blocked. Semaphores can help you easily control the rate of requests. In this example, Managing a fixed number of database connections is another great use case for semaphores. Here’s a simple example of a connection pool: This When processing large amounts of data in parallel, you might need to limit the number of concurrent operations due to memory or CPU constraints. Semaphores can help: This script processes multiple files concurrently but limits the number of files being processed at any given time to 5, preventing excessive memory usage. Semaphores can be used to implement a bounded queue for producer-consumer scenarios: Output: This To use this code: Semaphores are a great way to manage concurrency in async Python, but like any other synchronization primitive, they have drawbacks and limitations. Here are some of the main ones: To mitigate these drawbacks and limitations, you can use other synchronization primitives like async locks, which provide more advanced features and better support for async/await. You can also use libraries like Trio or Curio, which provide more advanced concurrency features and better support for async/await. Here’s an example of how you can use an async lock instead of a semaphore to manage concurrency in async Python: In this example, we use an async lock to synchronize access to a critical code section. The async lock provides better support for async/await and doesn’t block indefinitely if a task is canceled. When using semaphores, keep these tips in mind: Semaphores are a powerful tool in the async programmer’s toolkit. They can help you manage shared resources, implement rate limiting, control concurrency, and coordinate between producers and consumers. Understanding these use cases and patterns allows you to write more efficient and robust asynchronous Python code. Context
asyncio.Semaphore
in Python to manage the rate limit and avoid getting rate limit errors. We will start with the basics of semaphores and then move on to use cases. To directly jump to the code, you can check out the use case 5. Introduction
Setting Up: Semaphores in Python’s asyncio
asyncio.Semaphore
class provides a simple interface for creating and using semaphores:
await
=
=
await
async with
statement ensures that the semaphore is released even if an exception occurs. Use Case 1: Rate Limiting API Requests
=
=
return await
await
=
=
=
= await
await
RateLimitedClient
uses a semaphore to ensure that no more than 5 requests are made concurrently, effectively rate-limiting our API calls. Use Case 2: Connection Pool Management
=
=
= None
= await
return await
await
=
await
=
= await
await
DatabasePool
class uses a semaphore to limit the number of concurrent database connections, preventing connection exhaustion. Use Case 3: Parallel Data Processing with Resource Constraints
= await
# Process the content (e.g., parse JSON, transform data, etc.)
=
await
= # Process up to 5 files concurrently
=
=
await
Use Case 4: Implementing a Bounded Producer-Consumer Queue
=
=
=
=
await
await
await
= await
return
= f
await
await
= await
await
=
=
=
await
BoundedQueue
class uses two semaphores to ensure that the queue never exceeds its maximum size and that consumers wait when the queue is empty. Use Case 5: Calling OpenAI API
# Create a client instance
=
# Create a semaphore with a limit of 5 concurrent requests
# Adjust this number based on your API rate limits
=
= await
return ..
return None
=
= await
return
=
= await
"your-api-key-here"
with your actual OpenAI API key.Semaphore(5)
value if needed, based on your specific API rate limits. Drawbacks
# Critical section of code
...
await
# Critical section of code
...
await
=
await
Best Practices and Common Pitfalls
async with
for automatic release.asyncio.BoundedSemaphore
if you want to prevent accidental over-releasing. Conclusion
Further Reading
Related Articles