Сoroutines (корутини) – це особливий вид функцій у Python, які здатні зупиняти та відновлювати своє виконання. Вони лежать в основі асинхронного програмування і забезпечують неблокуюче виконання коду. Якщо звичайна функція виконується від початку до кінця без пауз, то корутина може тимчасово «призупинитись» і передати керування циклу подій (event loop), а потім продовжити роботу з того самого місця.
Основи корутин у Python
Уявімо, що програма виконує тривалу операцію – наприклад, робить HTTP-запит або читає великий файл з диска. Якщо використовувати звичайні функції, виконання «застрягне» у цьому місці, поки не надійде відповідь. Інші частини програми не зможуть працювати конкурентно (тобто виконуватись одночасно в межах одного потоку), і користувач бачитиме «підвисання».
Корутини вирішують цю проблему: вони дозволяють тимчасово віддати керування циклу подій, щоб інші задачі могли виконуватись у той час, коли одна з них чекає на результат (наприклад, на відповідь від сервера).
Технічно, корутина – це об’єкт, який створюється при виклику асинхронної функції (вона визначається за допомогою async def). У таких функціях можна використовувати оператор await для очікування інших корутин чи асинхронних операцій. Саме await і є точкою, де виконання може «призупинитись».
У Python підтримка корутин реалізована через стандартний модуль asyncio, який надає цикл подій (event loop) та набір інструментів для роботи з асинхронними завданнями: asyncio.run(), asyncio.sleep(), asyncio.gather(), asyncio.create_task() тощо.
Визначення корутин
Щоб створити асинхронну функцію (яка повертає корутину), треба оголосити її з async def:
import asyncio
async def my_coroutine():
print('Start')
# await "призупиняє" my_coroutine і віддає керування циклу подій
await asyncio.sleep(1) # імітація асинхронної I/O операції
print('End')Пояснення:
- async def – повідомляє інтерпретатору, що це асинхронна функція (coroutine function).
- await – позначає «точку призупинки». Виконання поточної корутини my_coroutine зупиниться тут, і керування буде передано циклу подій, який може запустити інші задачі. Коли asyncio.sleep(1) завершиться, цикл подій відновить my_coroutine з цього ж місця.
Такі функції відрізняються від звичайних тим, що їх виклик не виконує тіло функції одразу, а миттєво повертає об’єкт корутини (coroutine object).
Цей об’єкт сам по собі ще нічого не робить – він є, по суті, планом виконання. Його потрібно передати в цикл подій (event loop) для виконання (наприклад, asyncio.run()).
Запуск корутин
Створений об’єкт-корутина сам по собі нічого не робить. Щоб він запрацював, його потрібно «підхопити» циклом подій (event loop). У Python для цього є модуль asyncio.
1. Простий запуск через asyncio.run()
Цей спосіб використовується найчастіше для головної функції програми. Він автоматично створює новий цикл подій, виконує передану корутину і закриває цикл після її завершення:
import asyncio
async def greet(name):
print(f'Hello, {name}!')
await asyncio.sleep(1)
print(f'Goodbye, {name}!')
async def main():
await greet('Alex')
asyncio.run(main())Тут main() – це виклик, що створює головну корутину, яку asyncio.run() запускає. Ця корутина, в свою чергу, викликає інші (greet) через await.
2. Виконання кількох корутин одночасно
Справжня сила корутин – у конкурентному виконанні. Замість того, щоб чекати завершення кожної задачі по черзі (await task1, await task2), можна запустити їх разом за допомогою asyncio.gather:
import asyncio
async def task(name, delay):
await asyncio.sleep(delay)
print(f'Task {name} finished after {delay} sec')
async def main():
await asyncio.gather(
task('A', 2),
task('B', 1),
task('C', 3),
)
asyncio.run(main())У цьому прикладі asyncio.gather отримує три об’єкти-корутини. Всі три задачі стартують конкурентно (тобто одночасно вносяться до циклу подій). task('C') має найбільшу затримку, а результат з’являється поступово, в міру їх фактичного завершення:
Task B finished after 1 sec
Task A finished after 2 sec
Task C finished after 3 sec3. Створення задач через create_task()
Інший підхід – створити окремі «задачі» (Tasks). Task – це об’єкт, що обгортає корутину і планує її виконання у циклі подій негайно:
import asyncio
async def worker():
print('Worker started')
await asyncio.sleep(2)
print('Worker finished')
async def main():
# Плануємо worker() на виконання і одразу рухаємось далі
task = asyncio.create_task(worker())
await asyncio.sleep(1)
print('Main is doing something else...')
# Тепер чекаємо на завершення фонової задачі
await task
asyncio.run(main())У цьому прикладі worker() працює конкурентно з основним кодом main(). main призупиняється на 1 секунду, в той час як worker продовжує працювати у фоні:
Worker started
Main is doing something else...
Worker finishedПідсумок
Корутини роблять програми більш ефективними: вони не блокують виконання під час очікування (наприклад, I/O-операцій), а дозволяють іншим завданням працювати конкурентно. Це особливо важливо там, де потрібно обробляти багато одночасних операцій – мережеві запити, роботу з базами даних, асинхронну взаємодію з файлами.
Завдяки корутинам Python отримав ефективну модель асинхронного програмування, яка дозволяє створювати швидкі веб-сервіси, відгукливі клієнтські програми (наприклад, з графічним інтерфейсом) та інші високопродуктивні системи.