← Назад к вопросам

Как работает Event Loop?

3.0 Senior🔥 141 комментариев
#Python Core#Асинхронность и многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает Event Loop

Event Loop (цикл событий) — это сердце асинхронного программирования. Это бесконечный цикл, который управляет выполнением асинхронного кода, переключаясь между ними когда они ждут (например, сеть, файлы).

Базовая идея

┌──────────────────────────────────┐
│   Event Loop (бесконечный цикл)  │
├──────────────────────────────────┤
│ 1. Есть ли готовые задачи?      │
│    → Выполнить одну             │
│ 2. Нет готовых задач?           │
│    → Подождать события           │
│ 3. Вернуться на шаг 1            │
└──────────────────────────────────┘

Как Python распределяет время

import asyncio

async def task1():
    print("Task 1: начало")
    await asyncio.sleep(1)  # Уступает управление (I/O-bound)
    print("Task 1: конец")

async def task2():
    print("Task 2: начало")
    await asyncio.sleep(0.5)  # Уступает управление
    print("Task 2: конец")

async def main():
    # Запустить обе задачи конкурентно
    await asyncio.gather(task1(), task2())

# Выполнение:
# Task 1: начало
# Task 2: начало
# (Event Loop ждёт события от ОС)
# Task 2: конец        (через 0.5 сек)
# Task 1: конец        (через 1.0 сек)
# Общее время: 1 сек (не 1.5!)

Внутреннее устройство

Event Loop имеет очередь задач (queue) и отслеживает статус каждой:

┌─────────────────────────────────┐
│  Event Loop State                │
├─────────────────────────────────┤
│ Ready Queue:                    │
│  - task_1 (готова к выполнению) │
│  - task_2 (готова к выполнению) │
│                                 │
│ Waiting Queue:                  │
│  - task_3 (ждёт I/O)           │
│  - task_4 (ждёт таймер)        │
└─────────────────────────────────┘

Пошагово: Event Loop в действии

import asyncio

async def fetch_data(url, delay):
    print(f"Начало: {url}")
    await asyncio.sleep(delay)  # Имитация сетевого запроса
    print(f"Завершено: {url}")
    return f"Данные с {url}"

async def main():
    print("Шаг 0: Event Loop начинает")
    
    # Создаём задачи
    task1 = asyncio.create_task(fetch_data("api/users", 1))
    task2 = asyncio.create_task(fetch_data("api/posts", 0.5))
    
    print("Шаг 1: Обе задачи созданы (готовы к запуску)")
    
    results = await asyncio.gather(task1, task2)
    
    print(f"Шаг 2: Все задачи завершены: {results}")

asyncio.run(main())

# Вывод:
# Шаг 0: Event Loop начинает
# Шаг 1: Обе задачи созданы (готовы к запуску)
# Начало: api/users
# Начало: api/posts
# (Event Loop ждёт события от ОС)
# Завершено: api/posts     (0.5 сек прошла)
# Завершено: api/users     (1 сек прошла)
# Шаг 2: Все задачи завершены

Event Loop vs Threads

Threads (многопоточность):

Поток 1: ▓▓▓▓ (работает) ░░░ (ждёт) ▓▓▓▓ (работает)
Поток 2:     ▓▓▓▓ (работает) ░░░ (ждёт)
Время: ████████ (выполняются параллельно)

Event Loop (асинхронность):

Задача 1: ▓▓▓▓ (работает) ░░░ (ждёт) ▓▓▓▓ (работает)
Задача 2:      ▓▓▓▓ (работает) ░░░ (ждёт)
Время: ▓▓▓▓▓▓▓▓▓ (выполняется последовательно, но ждёт не ваш CPU)

Разница: в asyncio ждущие операции не занимают CPU.

Await — как это работает

async def example():
    print("До await")
    result = await some_async_function()  # Уступает управление Event Loop
    print("После await")
    return result

# Когда выполняется await:
# 1. Текущая задача приостанавливается
# 2. Event Loop берёт другую задачу из очереди
# 3. Выполняет её до следующего await
# 4. Когда async функция завершится, Event Loop вернёт управление

Практический пример: Web Requests

import asyncio
import aiohttp

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()  # Уступает управление

async def main():
    async with aiohttp.ClientSession() as session:
        urls = [
            'https://api.github.com/users/github',
            'https://api.github.com/users/google',
            'https://api.github.com/users/microsoft',
        ]
        
        # 3 сетевых запроса выполняются конкурентно
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        
        return results

# Время выполнения:
# - Последовательно: 3 × 0.5 сек = 1.5 сек
# - Параллельно (asyncio): 0.5 сек (макс из всех)
asyncio.run(main())

Event Loop и CPU-bound код

ОПАСНО: если async функция не уступает управление, она блокирует весь Event Loop!

async def bad_example():
    # ❌ Это блокирует Event Loop!
    total = 0
    for i in range(1_000_000_000):
        total += i
    return total

async def main():
    task1 = asyncio.create_task(bad_example())
    task2 = asyncio.create_task(asyncio.sleep(0.1))
    
    await asyncio.gather(task1, task2)
    # task2 может ждать ДОЛГО, потому что task1 заблокирована

Решение: используй run_in_executor для CPU-bound кода

async def good_example():
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(
        None,  # Default executor
        cpu_bound_function,  # Функция
        arg1, arg2  # Аргументы
    )
    return result

Context Manager для Event Loop

async def task():
    await asyncio.sleep(1)
    return "Done"

# asyncio.run() создаёт Event Loop, запускает задачу, закрывает Loop
asyncio.run(task())

# Или вручную:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
    result = loop.run_until_complete(task())
finally:
    loop.close()

Отладка Event Loop

import asyncio

# Включить логирование
logging.basicConfig(level=logging.DEBUG)

async def main():
    # Получить текущий Event Loop
    loop = asyncio.get_event_loop()
    print(f"Event Loop: {loop}")
    print(f"Running tasks: {asyncio.all_tasks(loop)}")

asyncio.run(main())

Event Loop — фундамент асинхронного программирования в Python, критически важно понимать его поведение для написания эффективного кода.