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

Сколько потоков занимает работа разных корутин в asyncio?

2.2 Middle🔥 131 комментариев
#Python Core#Soft Skills#Архитектура и паттерны

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

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

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

Потоки и корутины в asyncio

Основная концепция

Корутины в asyncio занимают один поток — это главное отличие асинхронного программирования от многопоточности. Все корутины выполняются в рамках одного потока через event loop (цикл событий), который управляет переключением между ними.

Как это работает

Event loop — это планировщик, работающий в одном потоке. Когда корутина выполняет асинхронную операцию (например, сетевой запрос), она передаёт управление обратно в event loop через await, позволяя другим корутинам выполняться на том же потоке.

import asyncio

async def task1():
    print("Task 1 начал")
    await asyncio.sleep(1)  # Отдаёт контроль, не блокирует поток
    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())

asyncio.run(main())
# Output:
# Task 1 начал
# Task 2 начал
# Task 2 закончил
# Task 1 закончил

Проверка количества потоков

import asyncio
import threading

async def check_thread():
    # Все корутины будут напечатывать одинаковый ID потока
    print(f"Поток: {threading.current_thread().ident}")
    await asyncio.sleep(0.1)
    print(f"После sleep, поток: {threading.current_thread().ident}")

async def main():
    print(f"Main поток: {threading.current_thread().ident}")
    await asyncio.gather(
        check_thread(),
        check_thread(),
        check_thread()
    )

asyncio.run(main())

Когда может быть несколько потоков

Если вы явно используете run_in_executor(), то асинхронный код может вызывать функции в отдельных потоках из thread pool:

import asyncio
import threading

def blocking_operation():
    # Эта функция выполнится в отдельном потоке
    print(f"Blocking работает в потоке: {threading.current_thread().ident}")
    return "result"

async def main():
    loop = asyncio.get_event_loop()
    # Передаём блокирующую операцию в thread pool
    result = await loop.run_in_executor(None, blocking_operation)
    print(f"Main работает в потоке: {threading.current_thread().ident}")
    print(f"Результат: {result}")

asyncio.run(main())

Сравнение с потоками

ХарактеристикаasyncioThreading
Потоков1 (event loop)Много параллельных
ПереключениеДобровольное (await)Вынужденное (OS scheduler)
Контекст-переключениеМинимальноеДорогое
GILНе блокируетБлокирует (CPython)
Использование памятиНизкоеВысокое

Производительность

Для I/O-bound операций (сеть, файлы) asyncio намного эффективнее благодаря одному потоку и минимальным издержкам контекст-переключения. Одна корутина может ждать результата без блокировки других.

# Неэффективно: блокирует поток
import requests
import time

start = time.time()
for i in range(10):
    requests.get("https://example.com")
print(f"Потоки: {time.time() - start}s")  # ~10 секунд

# Эффективно: корутины в одном потоке
import aiohttp

async def fetch(session, url):
    async with session.get(url) as resp:
        return await resp.text()

async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, "https://example.com") for _ in range(10)]
        await asyncio.gather(*tasks)

asyncio.run(main())  # ~1 секунда

Заключение

Запомните: asyncio = 1 поток + множество корутин, переключаемых через event loop. Это позволяет обрабатывать тысячи одновременных I/O операций без лишних издержек многопоточности.