8.8. Lock

asyncio.Lock menyediakan mutual exclusion antar coroutine -- jaminan bahwa hanya satu coroutine pada satu waktu yang memegang lock, dan yang lain menunggu sampai pemegang melepaskannya.

8.8.1. Kapan lock diperlukan

Halaman konkurensi kooperatif mencatat bahwa dua coroutine tidak dapat saling silang di tengah kode yang tidak memiliki await di dalamnya. Sebaliknya juga berlaku: begitu sebuah coroutine awaits, loop bebas untuk menjalankan coroutine lain. Jika dua coroutine menyentuh sumber daya yang sama di antara awaits -- bus UART, I2C, atau SPI -- operasi mereka dapat saling silang dengan cara yang merusak sumber daya tersebut.

Lock di sekitar bagian kritis menutup celah tersebut:

import asyncio

bus_lock = asyncio.Lock()

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

Dua coroutine sekarang dapat memanggil read_register secara bersamaan; lock memastikan hanya satu dari mereka yang memegang bus pada satu waktu, dan yang lain menunggu lock dilepaskan sebelum memulai.

Lock tidak diperlukan ketika bagian kritis tidak memiliki await di dalamnya -- jaminan penjadwalan kooperatif sudah mencakup kasus itu. Lock hanya diperlukan ketika bagian kritis menyerahkan kontrol ke loop di tengah proses.

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.

Untuk kasus langka di mana masa hidup lock tidak sesuai dengan blok kode, metode-metodenya juga tersedia secara langsung:

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

try/finally diperlukan untuk membuat ini setara dengan versi async with. Bentuk async with ada karena ini adalah bentuk yang benar dan bahasa membuatnya ringkas.

8.8.3. Referensi metode

  • acquire() -- sebuah coroutine. Diblokir sampai lock tidak terkunci, kemudian mengambilnya.

  • release() -- melepaskan lock. Jika ada coroutine yang mengantri menunggu acquire(), yang berikutnya dalam antrian dijadwalkan untuk berjalan dan lock tetap terkunci; jika tidak, lock menjadi tidak terkunci.

  • locked() -- mengembalikan True jika lock saat ini dipegang, False jika tidak. Langsung kembali; tidak diblokir.

Waiter dilayani dalam urutan FIFO. Tidak ada prioritas, tidak ada reentrancy (task yang sama tidak dapat mengambil lock yang sudah dipegang), dan tidak ada timeout pada acquire. Untuk menetapkan deadline pada akuisisi lock, bungkus acquire dalam 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()