8.8. Zaključavanja

asyncio.Lock osigurava uzajamno isključivanje među korutinama – jamstvo da u svakom trenutku samo jedna korutina drži zaključavanje, dok ostale čekaju da ga vlasnik otpusti.

8.8.1. Kada je zaključavanje potrebno

Stranica kooperativna konkurentnost napomenula je da se dvije korutine ne mogu ispreplitati usred koda koji u sebi nema nijedan await. Vrijedi i suprotno: čim korutina pozove await, petlja je slobodna pokrenuti drugu korutinu. Ako dvije korutine pristupaju istom resursu kroz await pozive – UART, I2C ili SPI sabirnici – njihove se operacije mogu ispreplesti na načine koji oštete resurs.

Zaključavanje oko kritičnog odjeljka zatvara tu prazninu:

import asyncio

bus_lock = asyncio.Lock()

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

Dvije korutine sada mogu istovremeno pozvati read_register; zaključavanje osigurava da samo jedna od njih u danom trenutku drži sabirnicu, a druga čeka da se zaključavanje otpusti prije nego što započne.

Zaključavanja nisu potrebna kada kritični odjeljak u sebi nema await – jamstvo kooperativnog rasporeda već pokriva taj slučaj. Potrebna su samo kada kritični odjeljak na pola puta prepušta kontrolu petlji.

8.8.2. Idiom 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.

Za rijetke slučajeve kada se životni vijek zaključavanja ne poklapa s blokom koda, metode su dostupne i izravno:

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

try/finally potreban je kako bi ovo bilo ekvivalentno verziji s async with. Oblik async with postoji upravo zato što je to ispravan oblik, a jezik ga čini sažetim.

8.8.3. Pregled metoda

  • acquire() – korutina. Blokira dok se zaključavanje ne otključa, zatim ga preuzima.

  • release() – otpušta zaključavanje. Ako su neke korutine u redu čekanja na acquire(), sljedeća u redu se zakazuje za izvođenje, a zaključavanje ostaje zaključano; u suprotnom zaključavanje postaje otključano.

  • locked() – vraća True ako se zaključavanje trenutno drži, inače False. Vraća odmah; ne blokira.

Čekatelji se poslužuju redoslijedom FIFO. Nema prioriteta, nema ponovnog ulaska (isti zadatak ne može preuzeti zaključavanje koje već drži) i nema isteka vremena pri preuzimanju. Da biste postavili rok na preuzimanje zaključavanja, omotajte preuzimanje u 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()