Сколько потоков занимает работа разных корутин в asyncio?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Потоки и корутины в 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())
Сравнение с потоками
| Характеристика | asyncio | Threading |
|---|---|---|
| Потоков | 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 операций без лишних издержек многопоточности.