8.8. Lock¶
asyncio.Lock cung cấp loại trừ lẫn nhau giữa các coroutine -- đảm bảo rằng chỉ một coroutine tại một thời điểm giữ khóa, và các coroutine khác chờ cho đến khi người giữ giải phóng nó.
8.8.1. Khi nào cần lock¶
Trang đồng thời hợp tác lưu ý rằng hai coroutine không thể xen kẽ nửa chừng qua đoạn code không có await trong đó. Ngược lại cũng đúng: ngay khi một coroutine await, loop tự do chạy coroutine khác. Nếu hai coroutine chạm cùng một tài nguyên qua các await -- UART, I2C, hoặc SPI bus -- các thao tác của chúng có thể xen kẽ theo những cách làm hỏng tài nguyên.
Một lock xung quanh critical section đóng khoảng trống:
import asyncio
bus_lock = asyncio.Lock()
async def read_register(bus, addr):
async with bus_lock:
bus.write(addr)
return await bus.read(2)
Hai coroutine có thể gọi read_register đồng thời; lock đảm bảo chỉ một trong số chúng giữ bus tại một thời điểm, và coroutine kia chờ lock được giải phóng trước khi bắt đầu.
Lock không cần khi critical section không có await bên trong nó -- đảm bảo lập lịch hợp tác đã bao gồm trường hợp đó. Chúng chỉ cần khi critical section nhường cho loop giữa chừng.
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.
Đối với trường hợp hiếm gặp khi vòng đời của lock không phù hợp với một khối code, các phương thức cũng có thể dùng trực tiếp:
await bus_lock.acquire()
try:
bus.write(addr)
result = await bus.read(2)
finally:
bus_lock.release()
try/finally là bắt buộc để làm cho nó tương đương với phiên bản async with. Dạng async with tồn tại vì đây là dạng đúng và ngôn ngữ làm cho nó ngắn gọn.
8.8.3. Tham chiếu phương thức¶
acquire()-- một coroutine. Chặn cho đến khi lock được mở khóa, sau đó chiếm lấy nó.release()-- giải phóng lock. Nếu có coroutine nào đang xếp hàng chờ trênacquire(), coroutine tiếp theo trong hàng được lên lịch chạy và lock vẫn bị khóa; nếu không, lock trở thành đã mở khóa.locked()-- trả vềTruenếu lock hiện đang được giữ,Falsenếu ngược lại. Trả về ngay lập tức; không chặn.
Các waiter được phục vụ theo thứ tự FIFO. Không có ưu tiên, không có reentrancy (cùng một task không thể acquire một lock mà nó đã giữ), và không có timeout trên acquire. Để đặt deadline cho việc acquire lock, bọc acquire trong 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()