Чем асинхронность отличается от многопоточности?
- Как работает event loop?
- Когда использовать asyncio, а когда потоки?
- Что такое корутины и виртуальные потоки?
Многопоточность — несколько потоков ОС, их переключает ядро. Асинхронность — одна (или немного) нить выполнения переключается между задачами сама, в точках ожидания (await).
- Асинхронность выгодна для большого числа ожидающих операций: сеть, база
- Для вычислений не помогает — один event loop занят, все ждут
- Блокирующий вызов внутри async-кода останавливает весь цикл
А ещё Виртуальные потоки (Java 21), горутины (Go) — дешёвые потоки с асинхронностью под капотом
Подробно
Конкурентность и параллелизм. Конкурентность — способность вести много задач, чередуя их; параллелизм — реально выполнять их одновременно на нескольких ядрах. Асинхронность даёт конкурентность, потоки — и то, и другое (кроме 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-функции.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Асинхронный код выполняет задачи параллельно на всех ядрах, а потоки ОС переключаются по очереди на одном ядре — поэтому async быстрее и для вычислений
- ✓Потоки переключает ядро ОС; асинхронный код сам переключается между задачами в точках ожидания — выгодно для множества ожидающих операций
- ✗Блокирующий вызов в async-коде затрагивает только свою задачу
- ✗Асинхронность и многопоточность — одно и то же
Что спросят дальше
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.