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¶
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.
สำหรับกรณีที่หายากที่อายุการใช้งานของ 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()