Какое количество Event Loop может быть в одном Python потоке?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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())
Выводы
- Один Event Loop на один поток — это правило
- Нельзя запустить два Event Loop одновременно в одном потоке
- Можно запустить Event Loop последовательно (но это редко нужно)
- Разные потоки могут иметь разные Event Loop — это нормально
- Event Loop не потокобезопасен — используй
call_soon_threadsafe()для взаимодействия - asyncio.run() — лучший способ для создания и управления Event Loop