8.8. Verrous#
asyncio.Lock assure l”exclusion mutuelle entre coroutines – la garantie qu’une seule coroutine à la fois détient le verrou, les autres attendant que le détenteur le libère.
8.8.1. Quand un verrou est nécessaire#
La page concurrence coopérative a souligné que deux coroutines ne peuvent pas s’entrelacer au milieu d’un code qui ne contient aucun await. L”inverse est également vrai : dès qu’une coroutine fait un await, la boucle est libre d’exécuter une autre coroutine. Si deux coroutines manipulent la même ressource entre des awaits – un bus UART, I2C ou SPI – leurs opérations peuvent s’entrelacer de manière à corrompre la ressource.
Un verrou autour de la section critique comble cette lacune
import asyncio
bus_lock = asyncio.Lock()
async def read_register(bus, addr):
async with bus_lock:
bus.write(addr)
return await bus.read(2)
Deux coroutines peuvent désormais appeler read_register simultanément ; le verrou garantit qu’une seule d’entre elles détient le bus à la fois, l’autre attendant que le verrou soit libéré avant de commencer.
Les verrous ne sont pas nécessaires lorsque la section critique ne contient aucun await – la garantie d’ordonnancement coopératif couvre déjà ce cas. Ils ne sont nécessaires que lorsque la section critique cède la main à la boucle en cours de route.
8.8.2. L’idiome async with#
L’exemple ci-dessus montre la manière recommandée d’utiliser un verrou : à l’intérieur d’un bloc async with. À l’entrée, le bloc fait un await sur acquire() ; à la sortie (que le bloc se termine normalement, lève une exception ou soit annulé), le verrou est libéré automatiquement. Il n’existe aucune voie de sortie du bloc qui laisse le verrou détenu.
Pour le cas rare où la durée de vie du verrou ne coïncide pas avec un bloc de code, les méthodes sont également disponibles directement
await bus_lock.acquire()
try:
bus.write(addr)
result = await bus.read(2)
finally:
bus_lock.release()
Le try/finally est requis pour rendre ceci équivalent à la version async with. La forme async with existe parce que c’est la forme correcte et que le langage la rend concise.
8.8.3. Référence des méthodes#
acquire()– une coroutine. Se bloque jusqu’à ce que le verrou soit déverrouillé, puis le prend.release()– libère le verrou. Si des coroutines sont en file d’attente suracquire(), la suivante dans la file est ordonnancée pour s’exécuter et le verrou reste verrouillé ; sinon, le verrou devient déverrouillé.locked()– renvoieTruesi le verrou est actuellement détenu,Falsesinon. Renvoie immédiatement ; ne se bloque pas.
Les attendeurs sont servis selon l’ordre FIFO. Il n’y a pas de priorité, pas de réentrance (la même tâche ne peut pas acquérir un verrou qu’elle détient déjà) et pas de délai d’expiration sur l’acquisition. Pour imposer une échéance à l’acquisition d’un verrou, encapsulez l’acquisition dans asyncio.wait_for()
try:
await asyncio.wait_for(bus_lock.acquire(), timeout=1)
except asyncio.TimeoutError:
# lock busy for >1 s -- bail out
return None
try:
...
finally:
bus_lock.release()