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 aacquire()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éntFalseé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()