8.1. Кооперативна конкурентність¶
Модель планування asyncio є кооперативною, а не витісняючою. Це розрізнення є найважливішою концептуальною моделлю, на якій будується решта цього розділу, тому варто розібратися з нею до появи будь-якого коду.
8.1.1. Витісняюче vs кооперативне планування¶
Витісняючий планувальник — який використовують настільні операційні системи для одночасного виконання багатьох програм — може зупинити будь-який фрагмент коду, що зараз виконується, в будь-який момент і перейти до іншого. Код, що виконується, не повинен нічого робити спеціально; планувальник перериває його. Це робить витісняюче планування дуже гнучким (жоден фрагмент коду не може заморити інші голодом через свою повільність), але це також означає, що будь-яку спільну змінну потрібно ретельно захищати, адже перемикання може відбутися будь-де — навіть посередині запису значення або під час читання списку.
Кооперативний планувальник може перемикатися між фрагментами коду лише в точках, де код, що виконується, явно передає управління назад. У asyncio такими точками є кожне await та кожен виклик корутини, яка всередині відмовляється від управління (найчастіше asyncio.sleep()). Між двома очікуваннями корутина, що виконується, має CPU лише для себе.
З цього випливають два наслідки:
Корутина, яка ніколи не чекає, ніколи не зупиняється. Якщо корутина знаходиться в тісному циклі без
awaitусередині, вона монополізує планувальник і нічого іншого не просувається. Виправлення полягає в тому, щоб вставитиawait asyncio.sleep_ms(0)(або інший виклик очікування) в доречному місці циклу.Спільний стан є безпечним між очікуваннями. Дві корутини не можуть чергуватися посередині операції, яка не містить
await. Тип пошкодження, що виникає при витісненні посередині багатокрокового оновлення — один фрагмент коду читає значення, поки інший перебуває в процесі його зміни — тут просто не може статися. Координація між корутинами все ще потрібна, коли кільком з них потрібно спільно використовувати ресурс між очікуваннями, але проблема чергування посередині рядка не стосується цього випадку.
8.1.2. Три рівні¶
Кожен asyncio-скрипт побудований з однакових трьох рівнів. Наступні дві сторінки детально їх розглядають; ці назви варто пам’ятати під час читання.
Корутини — функції, оголошені з
async def, кожна з яких є самостійною одиницею роботи, що очікує там, де це доречно. У огляді Python були представлені ключові словаasync/await; в asyncio вони є способом, яким корутина повертає управління планувальнику.Задачі — обгортка, яку
asyncio.create_task()розміщує навколо корутини, щоб запланувати її паралельно з поточною. Зазвичай застосунок створює невелику кількість задач для довготривалих робіт (цикл знімків, мережевий клієнт, зчитувач UART, …).Цикл подій — рушій знизу, який відстежує, які корутини чекають, а які готові до виконання, перемикаючись між задачами при кожному
await. Застосунок не пише цикл; він передає кореневу корутину вasyncio.run(), і цикл керує всім звідти.
Якщо застосунок описано таким чином — як невелика множина корутин, що компонуються циклом подій — конкурентність стає властивістю форми програми, а не чимось, чим застосунок повинен керувати крок за кроком.