8.8. Zámky#
asyncio.Lock poskytuje vzájemné vyloučení mezi korutinami – záruku, že zámek drží vždy pouze jedna korutina v daném okamžiku, zatímco ostatní čekají, dokud jej držitel neuvolní.
8.8.1. Kdy je zámek potřeba#
Stránka kooperativní souběžnost uváděla, že dvě korutiny se nemohou prokládat uprostřed kódu, který neobsahuje žádné await. Platí to i naopak: jakmile korutina zavolá await, smyčka může spustit jinou korutinu. Pokud dvě korutiny pracují se stejným prostředkem napříč await body – sběrnicí UART, I2C nebo SPI – jejich operace se mohou proložit způsobem, který prostředek poškodí.
Zámek kolem kritické sekce tuto mezeru uzavírá:
import asyncio
bus_lock = asyncio.Lock()
async def read_register(bus, addr):
async with bus_lock:
bus.write(addr)
return await bus.read(2)
Dvě korutiny nyní mohou obě souběžně volat read_register; zámek zajišťuje, že sběrnici drží vždy jen jedna z nich, a druhá čeká na uvolnění zámku, než začne.
Zámky nejsou potřeba, když kritická sekce neobsahuje uvnitř žádné await – na tento případ se již vztahuje záruka kooperativního plánování. Jsou potřeba pouze tehdy, když kritická sekce uprostřed předá řízení smyčce.
8.8.2. Idiom async with#
Výše uvedený příklad ukazuje doporučený způsob použití zámku: uvnitř bloku async with. Při vstupu blok zavolá await na acquire(); při výstupu (ať už blok skončil normálně, vyvolal výjimku, nebo byl zrušen) se zámek automaticky uvolní. Neexistuje žádná cesta z bloku, která by zámek ponechala drženým.
Pro vzácný případ, kdy se životnost zámku neshoduje s blokem kódu, jsou metody dostupné i přímo:
await bus_lock.acquire()
try:
bus.write(addr)
result = await bus.read(2)
finally:
bus_lock.release()
Konstrukce try/finally je nutná, aby byla ekvivalentní verzi async with. Forma async with existuje právě proto, že toto je správný tvar a jazyk jej činí stručným.
8.8.3. Přehled metod#
acquire()– korutina. Blokuje, dokud není zámek odemčen, a poté jej získá.release()– uvolní zámek. Pokud jsou nějaké korutiny ve frontě čekající naacquire(), je naplánováno spuštění další z fronty a zámek zůstává uzamčen; jinak se zámek odemkne.locked()– vracíTrue, pokud je zámek aktuálně držen, jinakFalse. Vrací se okamžitě; neblokuje.
Čekající jsou obsluhováni v pořadí FIFO. Neexistuje žádná priorita, žádná reentrance (stejná úloha nemůže získat zámek, který už drží) ani timeout při získávání. Chcete-li nastavit pro získání zámku časový limit, obalte získání do 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()