8.8. Zárak#

A asyncio.Lock kölcsönös kizárást biztosít a korutinok között – garantálja, hogy egyszerre csak egy korutin birtokolja a zárat, a többiek pedig várnak, amíg a birtokló elengedi azt.

8.8.1. Mikor van szükség zárra#

A kooperatív konkurencia oldal megjegyezte, hogy két korutin nem fűződhet össze egy olyan kódszakasz közepén, amelyben nincs await. Ennek az ellenkezője is igaz: amint egy korutin await-ol, a ciklus szabadon futtathat egy másik korutint. Ha két korutin awaiteken keresztül ugyanazt az erőforrást érinti – egy UART, I2C vagy SPI buszt –, a műveleteik olyan módon fűződhetnek össze, ami megrongálja az erőforrást.

Egy zár a kritikus szakasz köré bezárja ezt a rést:

import asyncio

bus_lock = asyncio.Lock()

async def read_register(bus, addr):
    async with bus_lock:
        bus.write(addr)
        return await bus.read(2)

Most már mindkét korutin egyidejűleg hívhatja a read_register függvényt; a zár gondoskodik arról, hogy egyszerre csak egyikük birtokolja a buszt, a másik pedig megvárja, amíg a zárat elengedik, mielőtt elindulna.

Zárakra nincs szükség, ha a kritikus szakaszon belül nincs await – a kooperatív ütemezés garanciája erre az esetre már gondoskodik. Csak akkor van rájuk szükség, ha a kritikus szakasz menet közben átadja a vezérlést a ciklusnak.

8.8.2. Az async with idióma#

A fenti példa a zár ajánlott használatát mutatja: egy async with blokkon belül. A blokkba belépéskor a blokk await-ol a acquire() hívásra; kilépéskor (akár normálisan tért vissza a blokk, akár kivételt dobott, akár megszakították) a zár automatikusan elengedődik. Nincs olyan kilépési út a blokkból, amely a zárat birtokolva hagyná.

Arra a ritka esetre, amikor a zár élettartama nem esik egybe egy kódblokkal, a metódusok közvetlenül is elérhetők:

await bus_lock.acquire()
try:
    bus.write(addr)
    result = await bus.read(2)
finally:
    bus_lock.release()

A try/finally szükséges ahhoz, hogy ez egyenértékű legyen az async with verzióval. Az async with forma azért létezik, mert ez a helyes alak, és a nyelv tömörré teszi.

8.8.3. Metódus-referencia#

  • acquire() – egy korutin. Addig blokkol, amíg a zár fel nem oldódik, majd megszerzi azt.

  • release() – elengedi a zárat. Ha bármely korutin a acquire() hívásra várakozva sorban áll, a sorban következő ütemezésre kerül, és a zár zárva marad; egyébként a zár feloldódik.

  • locked()True értéket ad vissza, ha a zárat jelenleg birtokolják, egyébként False értéket. Azonnal visszatér; nem blokkol.

A várakozók FIFO sorrendben kerülnek kiszolgálásra. Nincs prioritás, nincs újrabelépés (ugyanaz a feladat nem szerezhet meg egy olyan zárat, amelyet már birtokol), és nincs időtúllépés a megszerzésnél. Ahhoz, hogy határidőt szabjunk egy zár megszerzésére, csomagoljuk a megszerzést egy asyncio.wait_for() hívásba:

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()