8.8. Lukot

asyncio.Lock tarjoaa poissulkemisen (mutual exclusion) korutiinien välillä – takuun siitä, että vain yksi korutiini kerrallaan pitää lukkoa hallussaan, ja muut odottavat, kunnes haltija vapauttaa sen.

8.8.1. Milloin lukkoa tarvitaan

yhteistyöhön perustuva rinnakkaisuus -sivulla todettiin, että kaksi korutiinia ei voi lomittua kesken sellaisen koodin, jossa ei ole await-kutsua. Myös päinvastainen pätee: heti kun korutiini suorittaa await-kutsun, silmukka voi vapaasti ajaa toista korutiinia. Jos kaksi korutiinia käsittelee samaa resurssia await-kutsujen yli – UART-, I2C- tai SPI-väylää – niiden operaatiot voivat lomittua tavoilla, jotka turmelevat resurssin.

Lukko kriittisen osan ympärillä sulkee tämän aukon:

import asyncio

bus_lock = asyncio.Lock()

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

Kaksi korutiinia voi nyt molemmat kutsua read_register-funktiota samanaikaisesti; lukko varmistaa, että vain yksi niistä pitää väylää hallussaan kerrallaan, ja toinen odottaa lukon vapautumista ennen aloittamista.

Lukkoja ei tarvita, kun kriittisessä osassa ei ole await-kutsua – yhteistyöhön perustuvan vuorottamisen takuu kattaa jo tämän tapauksen. Niitä tarvitaan vain, kun kriittinen osa luovuttaa vuoron silmukalle kesken suorituksen.

8.8.2. async with -idiomi

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.

Niitä harvinaisia tapauksia varten, joissa lukon elinkaari ei vastaa koodilohkoa, metodit ovat saatavilla myös suoraan:

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

try/finally on pakollinen, jotta tämä vastaa async with -versiota. async with -muoto on olemassa, koska tämä on oikea rakenne ja kieli tekee siitä tiiviin.

8.8.3. Metodiviite

  • acquire() – korutiini. Estää suorituksen, kunnes lukko on auki, ja ottaa sen sitten haltuunsa.

  • release() – vapauttaa lukon. Jos jokin korutiini on jonossa odottamassa acquire()-kutsussa, jonon seuraava ajoitetaan suoritettavaksi ja lukko pysyy lukittuna; muutoin lukko avautuu.

  • locked() – palauttaa True, jos lukkoa pidetään parhaillaan, muutoin False. Palautuu välittömästi; ei estä suoritusta.

Odottajia palvellaan FIFO-järjestyksessä. Prioriteetteja ei ole, ei uudelleentulopaikkaa (sama tehtävä ei voi ottaa haltuunsa jo pitämäänsä lukkoa) eikä aikakatkaisua haltuunotossa. Aikarajan asettamiseksi lukon haltuunotolle kääri haltuunotto asyncio.wait_for()-funktioon:

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