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¶
The example above shows the recommended way to use a lock:
inside an async with block. On entry the block awaits acquire(); on exit (whether the block
returned normally, raised an exception, or was cancelled)
the lock is released automatically. There is no path out of
the block that leaves the lock held.
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()