8.1. การทำงานพร้อมกันแบบร่วมมือ¶
โมเดลการจัดตารางของ asyncio เป็นแบบ cooperative ไม่ใช่ preemptive ความแตกต่างนี้คือโมเดลทางความคิดที่สำคัญที่สุดซึ่งส่วนที่เหลือของหัวข้อนี้สร้างขึ้น จึงคุ้มค่าที่จะทำความเข้าใจก่อนที่จะมีโค้ดใดๆ
8.1.1. Preemptive เทียบกับ cooperative¶
ตัวจัดตาราง preemptive — แบบที่ระบบปฏิบัติการเดสก์ท็อปใช้เพื่อให้โปรแกรมหลายโปรแกรมทำงานพร้อมกัน — สามารถหยุดโค้ดที่กำลังทำงานอยู่ได้ ทุกขณะ และสลับไปอีกโปรแกรมหนึ่ง โค้ดที่กำลังทำงานไม่จำเป็นต้องทำอะไรพิเศษ ตัวจัดตารางจะขัดจังหวะเอง ทำให้ preemptive scheduling มีความยืดหยุ่นมาก (ไม่มีโค้ดชิ้นใดทำให้ชิ้นอื่นไม่ได้ทำงานเพราะช้า) แต่ก็หมายความว่าตัวแปรที่ใช้ร่วมกันต้องได้รับการป้องกันอย่างระมัดระวัง เพราะการสลับอาจเกิดขึ้นได้ทุกที่ — แม้กระทั่งระหว่างการเขียนค่าหรือระหว่างการอ่าน list
ตัวจัดตาราง cooperative สามารถสลับระหว่างโค้ดได้เฉพาะที่จุดที่โค้ดที่กำลังทำงาน ส่งคืนการควบคุมอย่างชัดเจน บน asyncio จุดเหล่านั้นคือทุก await และทุกการเรียก coroutine ที่ yield ภายใน (ส่วนใหญ่คือ asyncio.sleep()) ระหว่างสอง await coroutine ที่กำลังทำงานมี CPU เป็นของตัวเอง
ผลลัพธ์สองประการที่ตามมาคือ:
coroutine ที่ไม่เคย await จะไม่ถูกหยุดชั่วคราว หาก coroutine อยู่ใน tight loop โดยไม่มี
awaitภายใน มันจะผูกขาด scheduler และไม่มีสิ่งอื่นใดคืบหน้า การแก้ไขคือawait asyncio.sleep_ms(0)(หรือการเรียกรอแบบอื่น) ที่จุดที่เหมาะสมใน loopสถานะที่ใช้ร่วมกันปลอดภัยระหว่าง await สอง coroutine ไม่สามารถสลับกันกลางการดำเนินการที่ไม่มี
awaitได้ การเสียหายแบบที่เกิดขึ้นเมื่อ preemption ตกกลางการอัปเดตหลายขั้นตอน — โค้ดชิ้นหนึ่งอ่านค่าขณะที่อีกชิ้นกำลังเปลี่ยนแปลง — ไม่สามารถเกิดขึ้นที่นี่ได้เลย การประสานงานระหว่าง coroutine ยังคงจำเป็นเมื่อหลาย coroutine ต้องใช้ทรัพยากรร่วมกัน ข้าม await แต่ปัญหาการสลับกันกลางบรรทัดไม่มีผลที่นี่
8.1.2. สามชั้น¶
สคริปต์ asyncio ทุกตัวสร้างจากสามชั้นเดียวกัน สองหน้าถัดไปครอบคลุมรายละเอียด นี่คือป้ายกำกับที่ควรจำไว้ขณะอ่าน
Coroutines — ฟังก์ชันที่ประกาศด้วย
async defแต่ละตัวเป็นหน่วยงานที่ดูแลตัวเองและ await ตามความเหมาะสม Python Overview แนะนำคีย์เวิร์ดasync/awaitแล้ว ใน asyncio นี่คือวิธีที่ coroutine ส่งคืนการควบคุมกลับไปยัง schedulerTasks — wrapper ที่
asyncio.create_task()ห่อรอบ coroutine เพื่อ จัดตารางให้ทำงานพร้อมกันกับ coroutine ปัจจุบัน แอปพลิเคชันมักสร้าง task จำนวนหนึ่งสำหรับงานที่ทำงานต่อเนื่อง (loop สแนปช็อต, network client, UART reader, ...)Event loop — เอ็นจิ้นที่อยู่เบื้องหลังซึ่งติดตามว่า coroutine ใดกำลังรอและ coroutine ใดพร้อมทำงาน โดยสลับระหว่าง task ในทุก
awaitแอปพลิเคชันไม่ต้องเขียน loop เอง แต่ส่ง coroutine ระดับสูงสุดไปยังasyncio.run()และ loop จะขับเคลื่อนทุกอย่างจากที่นั่น
เมื่ออธิบายแอปพลิเคชันในลักษณะนั้น — เป็นชุด coroutine เล็กๆ ที่ประกอบกันโดย event loop — การทำงานพร้อมกันจะกลายเป็นคุณสมบัติของโครงสร้างโปรแกรม ไม่ใช่สิ่งที่แอปพลิเคชันต้องจัดการทีละขั้นตอน