> Какова иерархия процессов, потоков и asyncio в Python? (Python)

Уровень: senior · Роль: backend · Язык: Python · Категория: Технические вопросы

Компании: HeadHunter

Стек: Python

> Пример ответа

Короткий ответ

Иерархия выглядит так: процесс - самый верхний уровень, внутри процесса живут потоки, а asyncio работает в рамках одного потока, используя cooperative multitasking. Процессы изолированы друг от друга, потоки разделяют память процесса, а корутины asyncio выполняются в одном потоке на event loop. Выбор уровня зависит от задачи: CPU-bound - процессы, I/O-bound - asyncio или потоки, при этом asyncio легче и масштабируемее для большого числа конкурентных соединений.

Подробное объяснение

Процесс - это экземпляр программы, запущенный в операционной системе. Каждый процесс имеет собственное адресное пространство, таблицу файловых дескрипторов и как минимум один поток. Процессы изолированы: сбой одного не влияет на другие. В Python процессы создаются через multiprocessing или os.fork (на Unix). Из-за GIL процессы - единственный способ задействовать несколько ядер CPU для Python-кода.

Поток - это единица выполнения внутри процесса. Потоки одного процесса разделяют память и ресурсы, что делает их легче процессов, но требует синхронизации (locks, queues). В Python потоки ограничены GIL - только один поток исполняет Python-байткод в каждый момент времени. Поэтому потоки эффективны для I/O-bound задач, но не для CPU-bound.

asyncio - это библиотека для асинхронного программирования на основе event loop. В одном потоке выполняется множество корутин, которые добровольно отдают управление на операциях I/O (await). Корутины - это объекты, создаваемые из async-функций. Event loop распределяет время между корутинами, переключаясь только в точках await. Это позволяет обрабатывать десятки тысяч соединений без создания потоков.

Иерархия: процесс содержит потоки, поток содержит event loop asyncio. На практике это означает:

  • multiprocessing - для параллельных вычислений на нескольких ядрах.
  • threading - для конкурентного I/O, когда нужно блокирующие вызовы (например, старые библиотеки).
  • asyncio - для высоконагруженного сетевого I/O, когда важна масштабируемость и низкий overhead.

Важно: asyncio и потоки можно комбинировать - запускать корутины в отдельном потоке через asyncio.run_coroutine_threadsafe, или выполнять блокирующий код в loop.run_in_executor.

На практике

Для backend-разработчика выбор уровня зависит от характера нагрузки:

  • Если сервис делает много HTTP-запросов, обращений к БД, Redis - asyncio (FastAPI, aiohttp) даст лучшую производительность при большом числе одновременных соединений.
  • Если код использует синхронные библиотеки, которые не поддерживают async (например, старый ORM) - потоки через ThreadPoolExecutor или threading.
  • Если есть тяжёлые вычисления (парсинг, обработка изображений) - процессы через ProcessPoolExecutor, чтобы обойти GIL.

На практике часто используют гибрид: asyncio для основного цикла, а тяжёлые CPU-задачи отправляют в process pool через loop.run_in_executor.

Пример кода

PYTHON
import asyncio
import multiprocessing
import threading
import time
# asyncio - одна корутина на много задач
async def fetch_data(url):
await asyncio.sleep(0.1) # имитация I/O
return f"data from {url}"
async def main_async():
tasks = [fetch_data(f"url-{i}") for i in range(10)]
return await asyncio.gather(*tasks)
# потоки - для блокирующего I/O
def blocking_io(name):
time.sleep(0.1)
return f"thread {name} done"
def run_threads():
with threading.ThreadPoolExecutor(max_workers=4) as pool:
results = list(pool.map(blocking_io, range(10)))
return results
# процессы - для CPU-bound
def cpu_task(n):
return sum(i * i for i in range(n))
def run_processes():
with multiprocessing.Pool(4) as pool:
results = pool.map(cpu_task, [1000000] * 4)
return results
# запуск
if __name__ == "__main__":
print(asyncio.run(main_async()))
print(run_threads())
print(run_processes())

Как отвечать на собеседовании

Начни с краткого определения каждого уровня, затем покажи иерархию: процесс → потоки → event loop asyncio. Обязательно упомяни GIL и его влияние. Приведи практический пример выбора: CPU-bound → процессы, I/O-bound → asyncio, блокирующие библиотеки → потоки. Покажи понимание trade-off: память, overhead на переключение, сложность синхронизации. Если спросят про комбинирование - расскажи про run_in_executor и run_coroutine_threadsafe. Хорошо добавить упоминание, что asyncio не даёт параллелизма, а только конкурентность в одном потоке.

Что проверяет интервьюер

  • Понимание разницы между конкурентностью и параллелизмом.
  • Знание GIL и его последствий для потоков и процессов.
  • Умение выбирать правильный инструмент под задачу.
  • Понимание устройства event loop и cooperative multitasking.
  • Осведомлённость о способах интеграции asyncio с потоками и процессами.

Типичные ошибки

  • Утверждение, что asyncio ускоряет CPU-bound код - это не так, он только для I/O.
  • Путаница между конкурентностью и параллелизмом: asyncio и потоки дают конкурентность, процессы - параллелизм.
  • Забывают про GIL, когда говорят, что потоки в Python могут выполняться на разных ядрах.
  • Считают, что корутины - это то же самое, что потоки, но легче - на самом деле это другой механизм, без вытесняющего планировщика.
  • Не упоминают, что asyncio требует, чтобы весь код в цепочке был async - нельзя просто вызвать блокирующую функцию внутри корутины.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью