Sémaphore pour limiter les requêtes simultanées
Mis à jour le 30 juillet 2026
Le problème de la concurrence non contrôlée
Envoyer toutes vos requêtes en parallèle sans limitation est une erreur courante. Si vous avez 200 questions à traiter et que vous les lancez toutes simultanément, vous allez saturer les rate limits de l’API, recevoir des erreurs HTTP 429, et potentiellement voir votre clé API temporairement bloquée.

Le sémaphore est la solution standard pour contrôler le nombre de requêtes simultanées. C’est un compteur qui autorise un nombre maximal de coroutines à s’exécuter en parallèle.
Fonctionnement du sémaphore
asyncio.Semaphore agit comme un verrou à compteur. Quand une coroutine entre dans le bloc async with semaphore, le compteur diminue. Quand elle en sort, le compteur remonte. Si le compteur est à zéro, les coroutines suivantes attendent qu’une place se libère.
import asyncio
from asyncio import Semaphore
semaphore = Semaphore(5) # maximum 5 requetes simultanees
async def process_request(request: str):
async with semaphore:
# Cette section est limitée a 5 exécutions parallèles
chat = client.chat.create(
model="grok-4.20-0309-reasoning",
max_tokens=100
)
chat.append(user(request))
return await chat.sample()
Dans cet exemple, même si vous lancez 100 coroutines avec asyncio.gather, seules 5 s’executeront simultanément. Les 95 autres attendront leur tour.
Pattern complet avec le SDK OpenAI
Voici le même pattern avec AsyncOpenAI, qui est souvent préféré pour sa compatibilité :
from openai import AsyncOpenAI
import httpx
client = AsyncOpenAI(
api_key=os.getenv("XAI_API_KEY"),
base_url="https://api.x.ai/v1",
timeout=httpx.Timeout(3600.0)
)
async def send_request(sem: Semaphore, request: str) -> dict:
async with sem:
return await client.chat.completions.create(
model="grok-4.20-0309-reasoning",
messages=[{"role": "user", "content": request}]
)
async def process_all(requests: list[str], max_concurrent: int = 5):
sem = Semaphore(max_concurrent)
tasks = [send_request(sem, r) for r in requests]
return await asyncio.gather(*tasks)
Le paramètre max_concurrent est configurable. Vous le définissez en fonction de vos rate limits (leçon 7).
Choisir la bonne valeur de sémaphore
La valeur optimale se calcule à partir de vos rate limits, mais pas directement : elle passe par la durée des requêtes. Si votre tier autorise 60 RPM et que chaque requête dure environ 10 secondes, un sémaphore de 5 produit au maximum 5 requêtes simultanées, soit environ 30 requêtes par minute — confortablement sous la limite. C’est ce raisonnement (limite RPM × durée moyenne ÷ 60) qu’il faut refaire pour votre cas, et il explique pourquoi les modèles de raisonnement demandent un sémaphore plus bas : leurs requêtes durent plus longtemps, donc à concurrence égale, le débit par minute chute et le risque de saturation ponctuelle augmente. Dans le doute, la démarche empirique reste la plus sûre : commencez bas, entre 3 et 5, observez le taux d’erreurs 429, et montez progressivement jusqu’au point d’équilibre entre débit et stabilité.
Calcul approximatif
Requetes par minute = semaphore * (60 / duree_moyenne_secondes)
Exemple : semaphore=5, duree=10s → 5 * (60/10) = 30 RPM
Exemple : semaphore=10, duree=5s → 10 * (60/5) = 120 RPM
Erreur courante : sémaphore global vs local
# MAUVAIS : nouveau semaphore a chaque appel
async def bad_request(request: str):
sem = Semaphore(5) # chaque appel cree son propre semaphore
async with sem:
return await client.chat.completions.create(...)
# BON : semaphore partage entre tous les appels
sem = Semaphore(5)
async def good_request(request: str):
async with sem:
return await client.chat.completions.create(...)
Le sémaphore doit être partagé entre toutes les coroutines. S’il est créé localement dans chaque fonction, chaque coroutine aura son propre sémaphore et la limitation ne fonctionnera pas.
Points clés à retenir
- Le sémaphore limite le nombre de requêtes API simultanées pour respecter les rate limits
- Utilisez
async with semaphorepour protéger les appels API - Commencez avec une valeur de 3 à 5 et ajustez selon vos limites RPM
- Le sémaphore doit être partagé (global ou passe en paramètre), jamais crée localement
- La formule
RPM = semaphore * (60 / duree_moyenne)aide à dimensionner la valeur