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#
Yllä oleva esimerkki näyttää suositellun tavan käyttää lukkoa: async with -lohkon sisällä. Lohkoon tultaessa se suorittaa await-kutsun acquire()-metodille; poistuttaessa (palasi lohko sitten normaalisti, nosti poikkeuksen tai peruutettiin) lukko vapautetaan automaattisesti. Lohkosta ei ole mitään ulospääsyreittiä, joka jättäisi lukon pidetyksi.
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 odottamassaacquire()-kutsussa, jonon seuraava ajoitetaan suoritettavaksi ja lukko pysyy lukittuna; muutoin lukko avautuu.locked()– palauttaaTrue, jos lukkoa pidetään parhaillaan, muutoinFalse. 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()