8.8. Kilitler

asyncio.Lock, coroutine’ler arasında karşılıklı dışlama sağlar – aynı anda yalnızca bir coroutine’in kilidi tuttuğunu, diğerlerinin ise tutan coroutine kilidi serbest bırakana kadar beklediğini garanti eder.

8.8.1. Bir kilide ne zaman ihtiyaç duyulur

işbirlikçi eşzamanlılık sayfası, iki coroutine’in içinde await bulunmayan bir kod parçasının yarısında iç içe geçemeyeceğini belirtmişti. Bunun tersi de doğrudur: bir coroutine await yaptığı anda, döngü başka bir coroutine’i çalıştırmakta serbesttir. İki coroutine, await’ler boyunca aynı kaynağa – bir UART, I2C veya SPI veri yoluna – dokunursa, işlemleri kaynağı bozacak şekilde iç içe geçebilir.

Kritik bölümün etrafındaki bir kilit bu boşluğu kapatır:

import asyncio

bus_lock = asyncio.Lock()

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

İki coroutine artık read_register çağrısını eşzamanlı olarak yapabilir; kilit, aynı anda yalnızca birinin veri yolunu tutmasını sağlar ve diğeri başlamadan önce kilidin serbest bırakılmasını bekler.

Kritik bölümün içinde await yoksa kilitlere gerek yoktur – işbirlikçi zamanlama garantisi bu durumu zaten kapsar. Kilitlere yalnızca kritik bölüm, yarısında döngüye denetimi bıraktığında ihtiyaç duyulur.

8.8.2. async with deyimi

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.

Kilidin ömrünün bir kod bloğuyla örtüşmediği nadir durumlar için, yöntemler doğrudan da kullanılabilir:

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

try/finally, bunu async with sürümüyle eşdeğer kılmak için gereklidir. async with biçimi, bunun doğru yapı olması ve dilin onu kısa ve öz hale getirmesi nedeniyle vardır.

8.8.3. Yöntem başvurusu

  • acquire() – bir coroutine. Kilit açık olana kadar bloklar, ardından onu alır.

  • release() – kilidi serbest bırakır. acquire() üzerinde bekleyen sıraya alınmış coroutine’ler varsa, sıradaki bir sonraki coroutine çalışmak üzere zamanlanır ve kilit kilitli kalır; aksi takdirde kilit açılır.

  • locked() – kilit şu anda tutuluyorsa True, aksi takdirde False döndürür. Hemen döner; bloklamaz.

Bekleyenlere FIFO sırasına göre hizmet edilir. Öncelik yoktur, yeniden girme yoktur (aynı görev zaten tuttuğu bir kilidi alamaz) ve acquire üzerinde zaman aşımı yoktur. Bir kilit edinimine son tarih koymak için acquire’ı asyncio.wait_for() ile sarın:

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