Получить доступ

Чем асинхронность отличается от многопоточности?

MiddleКонкурентность
Ещё спрашивают так:
  • Как работает event loop?
  • Когда использовать asyncio, а когда потоки?
  • Что такое корутины и виртуальные потоки?
Коротко

Многопоточность — несколько потоков ОС, их переключает ядро. Асинхронность — одна (или немного) нить выполнения переключается между задачами сама, в точках ожидания (await).

  • Асинхронность выгодна для большого числа ожидающих операций: сеть, база
  • Для вычислений не помогает — один event loop занят, все ждут
  • Блокирующий вызов внутри async-кода останавливает весь цикл

А ещё Виртуальные потоки (Java 21), горутины (Go) — дешёвые потоки с асинхронностью под капотом

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Конкурентность и параллелизм. Конкурентность — способность вести много задач, чередуя их; параллелизм — реально выполнять их одновременно на нескольких ядрах. Асинхронность даёт конкурентность, потоки — и то, и другое (кроме Python с GIL).

Потоки. Каждый поток — отдельный стек и сущность ядра. Пока поток ждёт сеть, ядро отдаёт процессор другому. Модель простая: код пишется последовательно. Ограничение — цена: память на стек, переключения через ядро. Тысячи потоков — нормально, сотни тысяч — уже нет.

Event loop. Один поток крутит цикл: спросить у ОС, какие сокеты готовы (epoll, kqueue), и продолжить задачи, которые их ждали. Задача выполняется до точки ожидания (await), там сама отдаёт управление — кооперативная многозадачность. Ожидающая задача — это небольшой объект в памяти, а не поток, поэтому их могут быть сотни тысяч. Так работают Node.js, nginx, asyncio в Python, Netty и WebFlux.

Цена асинхронности:

  • заразность — async функцию можно вызвать только из async; код делится на два «цвета»;
  • блокирующий вызов (синхронный драйвер базы, time.sleep, requests.get, тяжёлый цикл) останавливает весь event loop и все задачи. Такие вызовы выносят в пул потоков (run_in_executor);
  • сложнее отладка и стектрейсы, нужно следить за отменой и таймаутами.

Когда что:

  • много одновременных соединений, в основном ожидание — асинхронность (шлюзы, чаты, вебсокеты, прокси, скрейпинг);
  • вычисления — процессы или потоки по числу ядер;
  • обычный CRUD-сервис на сотни RPS — потоки проще и достаточно.

Лучшее из двух миров. Горутины в Go и виртуальные потоки Java 21: код пишется как обычный блокирующий, а рантайм при ожидании снимает лёгкий поток с потока ОС и ставит другой. Миллионы таких потоков возможны. Ограничения: в Java долго была проблема «прилипания» (pinning) при synchronized, исправленная в Java 24; и ресурсы вроде пула соединений к базе по-прежнему нужно ограничивать.

Пример

import asyncio, time

async def fetch(i):
    await asyncio.sleep(1)          # ожидание отдаёт управление циклу
    return i

async def main():
    t = time.perf_counter()
    await asyncio.gather(*(fetch(i) for i in range(1000)))
    print(time.perf_counter() - t)  # ~1 с для 1000 «запросов» в одном потоке

asyncio.run(main())
# time.sleep(1) вместо await asyncio.sleep(1) — 1000 секунд: цикл заблокирован

Что хотят услышать

  • Кто переключает: ядро (потоки) против самой программы (event loop).
  • Для чего асинхронность хороша и для чего нет.
  • Опасность блокирующих вызовов в async-коде.
  • Виртуальные потоки и горутины как компромисс.

Частые ошибки

  • «Асинхронный код выполняется параллельно» — в одном event loop нет.
  • Вызывать синхронный HTTP-клиент или драйвер базы внутри async-функции.

Что спросят дальше

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем