8.1. מקביליות שיתופית¶
מודל התזמון של asyncio הוא שיתופי (cooperative), ולא מקדים (preemptive). ההבחנה הזו היא המודל המנטלי החשוב ביותר שעליו נבנית שאר היחידה הזו, ולכן כדאי לקבע אותה לפני שמופיע קוד כלשהו.
8.1.1. מקדים מול שיתופי¶
מתזמן מקדים (preemptive) – מהסוג שמערכת הפעלה שולחנית משתמשת בו כדי לשמור על ריצת תוכניות רבות בו-זמנית – יכול להשהות כל קטע קוד שרץ כרגע ברגע כלשהו ולעבור לקטע אחר. הקוד הרץ אינו צריך לעשות דבר מיוחד; המתזמן מפסיק אותו. הדבר הופך את התזמון המקדים לגמיש מאוד (אף קטע קוד אינו יכול להרעיב את האחרים בכך שהוא איטי), אך משמעותו גם שכל משתנה משותף חייב להישמר בקפידה, משום שהמעבר עלול לחול בכל מקום – אפילו באמצע כתיבת ערך, או בחלק מקריאה של רשימה.
מתזמן שיתופי (cooperative) יכול לעבור בין קטעי קוד רק בנקודות שבהן הקטע הרץ כרגע מחזיר את השליטה במפורש. ב-asyncio, נקודות אלו הן כל await וכל קריאה לקורוטינה שמניבה (yields) באופן פנימי (לרוב asyncio.sleep()). בין שתי קריאות await, הקורוטינה הרצה מחזיקה ב-CPU לעצמה.
מכך נובעות שתי תוצאות:
קורוטינה שלעולם אינה מבצעת await לעולם אינה מושהית. אם קורוטינה יושבת בלולאה צפופה ללא
awaitבתוכה, היא משתלטת על המתזמן ושום דבר אחר אינו מתקדם. הפתרון הואawait asyncio.sleep_ms(0)(או קריאת המתנה אחרת) בנקודה הגיונית בלולאה.מצב משותף בטוח בין קריאות await. שתי קורוטינות אינן יכולות להשתלב באמצע פעולה שאין בה
await. סוג השחיתות שמתעורר כאשר הקדמה (preemption) חלה באמצע עדכון רב-שלבי – קטע קוד אחד קורא ערך בעוד קטע אחר נמצא בעיצומו של שינויו – פשוט אינו יכול לקרות כאן. עדיין נדרש תיאום בין קורוטינות כאשר כמה מהן צריכות לחלוק משאב לאורך קריאות await, אך בעיית השילוב באמצע השורה אינה רלוונטית.
8.1.2. שלוש השכבות¶
כל סקריפט asyncio בנוי מאותן שלוש שכבות. שני העמודים הבאים מכסים אותן בפירוט; אלו התוויות שכדאי לזכור בעת קריאתם.
קורוטינות – פונקציות המוכרזות באמצעות
async def, שכל אחת מהן יחידת עבודה עצמאית המבצעת await במקום המתאים. סקירת Python הציגה את מילות המפתחasync/await; ב-asyncio הן הדרך שבה קורוטינה מניבה את השליטה חזרה למתזמן.משימות (Tasks) – עטיפה ש-
asyncio.create_task()מציבה סביב קורוטינה כדי לתזמן אותה במקביל לזו הנוכחית. האפליקציה בדרך כלל יוצרת קומץ משימות עבור העבודות ארוכות-הטווח (לולאת התמונות, לקוח הרשת, קורא ה-UART, …).לולאת האירועים (event loop) – המנוע שמתחת שעוקב אחר אילו קורוטינות ממתינות ואילו מוכנות לרוץ, ועובר בין משימות בכל
await. האפליקציה אינה כותבת את הלולאה; היא מוסרת קורוטינה ברמה העליונה אלasyncio.run()והלולאה מנהלת משם את הכל.
כשמתארים את האפליקציה כך – כקבוצה קטנה של קורוטינות המורכבות על ידי לולאת אירועים – המקביליות הופכת לתכונה של צורת התוכנית, ולא למשהו שעל האפליקציה לנהל צעד אחר צעד.