Как устроен механизм работы корутин?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Механизм работы корутин в Python
Корутины — это функции, которые могут приостанавливать своё выполнение и передавать управление другим корутинам, позволяя однопоточному коду работать с множеством одновременных операций. Это один из главных инструментов асинхронного программирования в современном Python.
Эволюция корутин
В Python существует несколько поколений корутин:
- Generator-based корутины (Python 3.4) — использовали @coroutine и yield from
- Native корутины (Python 3.5+) — async/await синтаксис
- Асинхронные генераторы (Python 3.6+) — комбинация async и yield
Как работают native корутины (async/await)
import asyncio
async def fetch_data(url):
print(f"Начало загрузки: {url}")
await asyncio.sleep(2)
print(f"Завершена загрузка: {url}")
return f"Данные с {url}"
async def main():
task1 = fetch_data("http://api.example.com/1")
task2 = fetch_data("http://api.example.com/2")
results = await asyncio.gather(task1, task2)
print(results)
asyncio.run(main())
Ключевые концепции
Event Loop (цикл событий)
Это сердце асинхронной программы:
- Отслеживает все корутины и задачи
- Когда корутина вызывает await, она отдаёт управление
- Loop проверяет другие готовые корутины
- Когда I/O завершится, корутина пробуждается
import asyncio
async def example():
loop = asyncio.get_running_loop()
print(f"Текущий цикл: {loop}")
asyncio.run(example())
await — точка перехвата
Когда вы пишете await, вы говорите: "Приостанови меня и отдай управление другим". Это может быть:
- Другая корутина
- I/O операция (сеть, диск)
- Таймер
async def example():
await asyncio.sleep(1) # OK
result = await some_async_func() # OK
# result = await 42 # ОШИБКА
Task vs Coroutine
Корутина — отложенное вычисление, не запущено:
async def my_coroutine():
await asyncio.sleep(1)
return "готово"
coro = my_coroutine() # Создана, но НЕ запущена
print(type(coro)) # coroutine
Task — обёртка над корутиной в event loop:
async def main():
task = asyncio.create_task(my_coroutine())
result = await task
results = await asyncio.gather(my_coroutine(), my_coroutine())
asyncio.run(main())
Управление множеством операций
async def main():
# gather — все параллельно
results = await asyncio.gather(
fetch_data("url1"),
fetch_data("url2"),
fetch_data("url3")
)
# wait — контроль завершения
tasks = [asyncio.create_task(fetch_data(f"url{i}")) for i in range(3)]
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.FIRST_COMPLETED
)
# as_completed — обработка по мере завершения
for coro in asyncio.as_completed(tasks):
result = await coro
print(f"Готово: {result}")
Важно: асинхронный ≠ многопоточный
Асинхронный код выполняется в одном потоке! Это распределение времени между операциями:
import asyncio
import threading
async def worker(id):
print(f"Worker {id} на потоке {threading.current_thread().name}")
await asyncio.sleep(1)
async def main():
await asyncio.gather(worker(1), worker(2), worker(3))
asyncio.run(main())
Это эффективнее многопоточности для I/O:
- Нет overhead переключения потоков
- Нет race conditions
- Нет deadlock'ов
Типичные ошибки
# Забыл await
async def main():
result = fetch_data("url") # Вернётся coroutine объект!
# Блокирующий код в async
async def main():
time.sleep(10) # Заморозит весь event loop!
# Правильно: await asyncio.sleep(10)
# Смешивание sync и async
def sync_function():
loop = asyncio.get_event_loop()
result = loop.run_until_complete(async_func()) # Очень плохо
Пример с httpx для параллельных запросов
import asyncio
import httpx
async def fetch_url(client, url):
response = await client.get(url)
return response.status_code
async def main():
urls = ["https://example.com", "https://google.com", "https://github.com"]
async with httpx.AsyncClient() as client:
status_codes = await asyncio.gather(
*[fetch_url(client, url) for url in urls]
)
print(f"Статусы: {status_codes}")
asyncio.run(main())
Вместо 3 последовательных запросов за 3 секунды — все 3 параллельно за ~1 секунду.
Заключение
Корутины — это мощный инструмент для масштабируемых приложений с тысячами одновременных I/O операций. Ключ к пониманию — рассмотрение их как добровольной кооперативной многозадачности, где корутины сами отдают управление при ожидании операций.