8.8. Lås

asyncio.Lock ger ömsesidig uteslutning mellan coroutiner – en garanti att endast en coroutine i taget håller låset, medan de övriga väntar tills innehavaren släpper det.

8.8.1. När ett lås behövs

Sidan om kooperativ samtidighet påpekade att två coroutiner inte kan flätas in mitt i kod som inte innehåller något await. Det motsatta gäller också: så snart en coroutine awaitar är loopen fri att köra en annan coroutine. Om två coroutiner rör samma resurs över awaits – en UART-, I2C- eller SPI-buss – kan deras operationer flätas samman på sätt som korrumperar resursen.

Ett lås kring den kritiska sektionen sluter glappet:

import asyncio

bus_lock = asyncio.Lock()

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

Två coroutiner kan nu båda anropa read_register samtidigt; låset säkerställer att endast en av dem håller bussen åt gången, medan den andra väntar på att låset ska släppas innan den startar.

Lås behövs inte när den kritiska sektionen inte har något await inuti sig – garantin för den kooperativa schemaläggningen täcker redan det fallet. De behövs endast när den kritiska sektionen lämnar över till loopen halvvägs igenom.

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

För det sällsynta fall där låsets livslängd inte sammanfaller med ett kodblock är metoderna också tillgängliga direkt:

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

try/finally krävs för att göra detta likvärdigt med async with-versionen. Formen async with finns eftersom detta är den rätta formen och språket gör den kortfattad.

8.8.3. Metodreferens

  • acquire() – en coroutine. Blockerar tills låset är upplåst och tar det sedan.

  • release() – släpper låset. Om några coroutiner står i kö och väntar på acquire(), schemaläggs nästa i kön att köra och låset förblir låst; annars blir låset upplåst.

  • locked() – returnerar True om låset för närvarande hålls, annars False. Returnerar omedelbart; blockerar inte.

Väntare betjänas i FIFO-ordning. Det finns ingen prioritet, ingen återinträdesförmåga (samma task kan inte ta ett lås som det redan håller) och ingen timeout vid acquire. För att sätta en tidsfrist på ett låsförvärv, slå in acquire i 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()