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í na acquire(), 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, jinak False. 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()