8.8. Blokady¶
asyncio.Lock zapewnia wzajemne wykluczanie pomiędzy korutynami – gwarancję, że w danej chwili tylko jedna korutyna utrzymuje blokadę, a pozostałe czekają, aż jej posiadacz ją zwolni.
8.8.1. Kiedy blokada jest potrzebna¶
Strona współbieżność kooperacyjna zwracała uwagę, że dwie korutyny nie mogą przeplatać się w połowie kodu, który nie zawiera w sobie await. Prawdą jest również odwrotność: gdy tylko korutyna wykona await, pętla może swobodnie uruchomić inną korutynę. Jeśli dwie korutyny korzystają z tego samego zasobu na przestrzeni operacji await – magistrali UART, I2C lub SPI – ich operacje mogą się przeplatać w sposób uszkadzający ten zasób.
Blokada wokół sekcji krytycznej zamyka tę lukę:
import asyncio
bus_lock = asyncio.Lock()
async def read_register(bus, addr):
async with bus_lock:
bus.write(addr)
return await bus.read(2)
Teraz obie korutyny mogą współbieżnie wywoływać read_register; blokada gwarantuje, że tylko jedna z nich w danej chwili utrzymuje magistralę, a druga czeka na zwolnienie blokady, zanim rozpocznie pracę.
Blokady nie są potrzebne, gdy sekcja krytyczna nie zawiera w sobie await – ten przypadek pokrywa już gwarancja harmonogramowania kooperacyjnego. Są potrzebne tylko wtedy, gdy sekcja krytyczna w trakcie swojego wykonywania oddaje sterowanie pętli.
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.
W rzadkich przypadkach, gdy czas życia blokady nie pokrywa się z blokiem kodu, metody są również dostępne bezpośrednio:
await bus_lock.acquire()
try:
bus.write(addr)
result = await bus.read(2)
finally:
bus_lock.release()
Konstrukcja try/finally jest wymagana, aby uczynić to równoważnym z wersją async with. Forma async with istnieje właśnie dlatego, że to jest właściwy kształt, a język czyni go zwięzłym.
8.8.3. Opis metod¶
acquire()– korutyna. Blokuje, dopóki blokada nie zostanie odblokowana, a następnie ją przejmuje.release()– zwalnia blokadę. Jeśli jakiekolwiek korutyny stoją w kolejce oczekując naacquire(), następna w kolejce zostaje zaplanowana do uruchomienia, a blokada pozostaje zablokowana; w przeciwnym razie blokada staje się odblokowana.locked()– zwracaTrue, jeśli blokada jest aktualnie utrzymywana, aFalsew przeciwnym razie. Zwraca natychmiast; nie blokuje.
Oczekujący są obsługiwani w kolejności FIFO. Nie ma priorytetów, nie ma rekurencyjności (to samo zadanie nie może przejąć blokady, którą już posiada) ani limitu czasu na przejęcie. Aby nałożyć termin na przejęcie blokady, opakuj operację acquire w 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()