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 จะกลายเป็น unlockedlocked()-- คืนค่า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()