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

Какое количество Event Loop может быть в одном Python потоке?

2.7 Senior🔥 121 комментариев
#Асинхронность и многопоточность

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

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

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

Event Loop в Python потоке

В одном Python потоке может быть только один активный Event Loop одновременно. Однако можно создать несколько Event Loop объектов последовательно (один за другим).

Основной принцип

Event Loop (цикл событий) — это сердце asyncio. Это объект, который управляет асинхронными операциями и коутинами в одном потоке. Правило такое:

One loop per thread — один Event Loop на один поток.

import asyncio
import threading

async def hello():
    print("Hello from coroutine")

def run_in_thread():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)  # Установить loop для текущего потока
    loop.run_until_complete(hello())
    loop.close()

# Главный поток
main_loop = asyncio.get_event_loop()

# Дополнительный поток с собственным Event Loop
thread = threading.Thread(target=run_in_thread)
thread.start()
thread.join()

print("Done")

Попытка создать несколько Event Loop в одном потоке — ошибка

import asyncio

async def test():
    await asyncio.sleep(1)
    print("Done")

# ✅ Правильно — создать один loop
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(test())
loop.close()

# ❌ ОШИБКА — попытка создать второй loop в том же потоке
try:
    loop2 = asyncio.new_event_loop()
    asyncio.set_event_loop(loop2)  # Это заменит loop, но старый остаётся в памяти
    loop2.run_until_complete(test())
    loop2.close()
except RuntimeError as e:
    print(f"Error: {e}")

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

Event Loop работает в цикле:

import asyncio

async def async_task(name, delay):
    print(f"{name} началась")
    await asyncio.sleep(delay)
    print(f"{name} закончилась")
    return f"{name} результат"

async def main():
    # Запуск трёх коутин ОДНОВРЕМЕННО в ОДНОМ Event Loop
    task1 = asyncio.create_task(async_task("Task1", 1))
    task2 = asyncio.create_task(async_task("Task2", 2))
    task3 = asyncio.create_task(async_task("Task3", 1.5))
    
    # Ждём всех
    results = await asyncio.gather(task1, task2, task3)
    print(results)

# ОДИН Event Loop управляет ТРЕМЯ коутинами
asyncio.run(main())

Этот один Event Loop переключается между коутинами, когда они ждут I/O или другие операции.

Структура Event Loop

┌─────────────────────┐
│   Event Loop        │
├─────────────────────┤
│                     │
│  1. Коллектор      │  (собирает события: I/O, таймеры)
│  2. Очередь задач  │  (хранит готовые коутины)
│  3. Выполнитель    │  (запускает коутины по одной)
│                     │
│  Цикл:             │
│  while running:    │
│    poll events     │
│    run callbacks   │
│    run all ready   │
│                     │
└─────────────────────┘

asyncio.run() в Python 3.7+

В Python 3.7 появился asyncio.run() — самый удобный способ запуска Event Loop:

import asyncio

async def main():
    await asyncio.sleep(1)
    print("Done")

# Это создаёт Event Loop, запускает main(), потом закрывает loop
asyncio.run(main())

# Эквивалентно:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
    loop.run_until_complete(main())
finally:
    loop.close()

Несколько Event Loop в разных потоках — ОК

import asyncio
import threading
import time

async def worker(name, delay):
    await asyncio.sleep(delay)
    print(f"{name} завершился в потоке {threading.current_thread().name}")

def run_loop_in_thread(name):
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.run_until_complete(worker(name, 1))
    loop.close()

# Разные потоки = разные Event Loop (это OK)
thread1 = threading.Thread(target=run_loop_in_thread, args=("Worker1",))
thread2 = threading.Thread(target=run_loop_in_thread, args=("Worker2",))

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print("Все завершены")

Проблема: Event Loop и потокобезопасность

Event Loop НЕ потокобезопасен. Если попытаться использовать один Event Loop из разных потоков, будет ошибка:

import asyncio
import threading

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

# Создаём loop в главном потоке
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

def try_use_loop_from_another_thread():
    # ❌ ОШИБКА — попытка использовать loop из другого потока
    try:
        loop.run_until_complete(task())
    except RuntimeError as e:
        print(f"Error: {e}")

thread = threading.Thread(target=try_use_loop_from_another_thread)
thread.start()
thread.join()

Правильное общение между Event Loop и потоками

Для отправки коутины в loop из другого потока используй loop.call_soon_threadsafe():

import asyncio
import threading
import time

async def async_task():
    await asyncio.sleep(1)
    print("Async task completed")

def run_event_loop(loop):
    asyncio.set_event_loop(loop)
    loop.run_forever()  # Бесконечный loop

# Главный loop в основном потоке
loop = asyncio.new_event_loop()
loop_thread = threading.Thread(target=run_event_loop, args=(loop,))
loop_thread.daemon = True
loop_thread.start()

# Из другого потока отправляем коутину в loop
time.sleep(0.5)  # Дождёмся запуска loop
loop.call_soon_threadsafe(
    asyncio.create_task,
    async_task()
)

time.sleep(2)
loop.call_soon_threadsafe(loop.stop)  # Остановить loop
loop_thread.join()

Количество задач в Event Loop

В одном Event Loop может быть много коутин и задач, но они выполняются последовательно (по слизанам):

import asyncio

async def task(name):
    await asyncio.sleep(0.1)
    return f"{name} done"

async def main():
    # Создаём 1000 задач в ОДНОМ Event Loop
    tasks = [task(f"Task{i}") for i in range(1000)]
    results = await asyncio.gather(*tasks)
    print(f"Выполнено {len(results)} задач в одном Event Loop")

asyncio.run(main())

Выводы

  1. Один Event Loop на один поток — это правило
  2. Нельзя запустить два Event Loop одновременно в одном потоке
  3. Можно запустить Event Loop последовательно (но это редко нужно)
  4. Разные потоки могут иметь разные Event Loop — это нормально
  5. Event Loop не потокобезопасен — используй call_soon_threadsafe() для взаимодействия
  6. asyncio.run() — лучший способ для создания и управления Event Loop