8.8. Locks#

asyncio.Lock ให้ mutual exclusion ระหว่าง coroutine -- การรับประกันว่ามีเพียง coroutine เดียวในแต่ละขณะที่ถือ lock และ coroutine อื่นรอจนกว่าผู้ถือจะปล่อยมัน

8.8.1. เมื่อใดที่ต้องการ lock#

หน้า concurrent concurrency แบบ cooperative ได้กล่าวไว้ว่าสอง coroutine ไม่สามารถสลับกันกลางโค้ดที่ไม่มี await ได้ ตรงกันข้าม ก็เป็นจริงเช่นกัน: ทันทีที่ coroutine await loop สามารถรัน coroutine อื่นได้ ถ้าสอง coroutine แตะทรัพยากรเดิมข้ามการ await -- bus UART, I2C, หรือ SPI -- การดำเนินการของพวกมันอาจสลับกันในแบบที่ทำให้ทรัพยากรเสียหาย

lock รอบ critical section จะปิดช่องโหว่:

import asyncio

bus_lock = asyncio.Lock()

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

ตอนนี้สอง coroutine สามารถเรียก read_register พร้อมกันได้; lock ทำให้มั่นใจว่ามีเพียงตัวเดียวที่ถือ bus ในแต่ละขณะ และตัวอื่นรอให้ lock ถูกปล่อยก่อนเริ่ม

lock ไม่ จำเป็นเมื่อ critical section ไม่มี await ภายใน -- การรับประกันของ cooperative scheduling ครอบคลุมกรณีนั้นอยู่แล้ว จำเป็นต้องใช้เฉพาะเมื่อ critical section ยอมส่งการควบคุมให้ loop กลางทาง

8.8.2. idiom async with#

ตัวอย่างข้างต้นแสดงวิธีที่แนะนำในการใช้ lock: ภายในบล็อก async with เมื่อเข้าบล็อก มัน await acquire(); เมื่อออก (ไม่ว่าบล็อกจะคืนค่าปกติ, ยกข้อผิดพลาด, หรือถูกยกเลิก) lock จะถูกปล่อยโดยอัตโนมัติ ไม่มีทางออกจากบล็อกที่ทิ้ง lock ค้างไว้

สำหรับกรณีที่หายากที่อายุการใช้งานของ lock ไม่ตรงกับบล็อกของโค้ด เมธอดต่างๆ ก็สามารถเรียกโดยตรงได้:

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

try/finally จำเป็นเพื่อให้เทียบเท่ากับเวอร์ชัน async with รูปแบบ async with มีอยู่เพราะ นี่ คือรูปแบบที่ถูกต้อง และภาษาทำให้มันกระชับ

8.8.3. เอกสารอ้างอิงเมธอด#

  • acquire() -- coroutine บล็อกจนกว่า lock จะถูกปล่อย แล้วจึงรับมัน

  • release() -- ปล่อย lock ถ้ามี coroutine ที่รอ acquire() อยู่, ตัวถัดไปในคิวจะถูกกำหนดให้ทำงานและ lock ยังคงถูกล็อก; มิฉะนั้น lock จะกลายเป็น unlocked

  • locked() -- คืนค่า True ถ้า lock ถูกถือไว้ในขณะนี้, False มิฉะนั้น คืนค่าทันที ไม่บล็อก

Waiter ได้รับการบริการตามลำดับ FIFO ไม่มีลำดับความสำคัญ, ไม่มี reentrancy (งานเดิมไม่สามารถ acquire lock ที่มันถืออยู่แล้ว), และไม่มี timeout สำหรับ acquire เพื่อกำหนด deadline สำหรับการ acquire lock ให้ห่อ acquire ใน 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()