Синхронный и асинхронный код: зачем

Разбираемся, что значит «асинхронный», и когда он реально ускоряет программу, а когда нет.

Синхронный код выполняет операции строго по очереди: следующая строка ждёт, пока завершится предыдущая. Асинхронный код умеет переключаться на другую работу, пока текущая операция ждёт ответа извне.

Проблема ожидания

Представьте программу, которая загружает три страницы из интернета. Каждый запрос — это в основном ожидание: вы отправили запрос и сидите, пока придёт ответ. Процессор в это время простаивает. Синхронный код будет ждать первый ответ, потом второй, потом третий — суммарно три ожидания подряд.

Асинхронный подход позволяет отправить все три запроса и ждать их одновременно. Пока один запрос «висит», программа занимается другими. Время выполнения становится близким к одному ожиданию вместо трёх.

I/O-bound против CPU-bound

Ключевое различие, которое определяет выбор инструмента:

Тип задачиЧем ограниченаПримеры
I/O-boundожиданием ввода-вывода (сеть, диск, БД)HTTP-запросы, чтение файлов, запросы к базе
CPU-boundвычислениями процессораобработка изображений, шифрование, математика

Запомните главное правило: асинхронность и потоки помогают I/O-bound задачам (где мы много ждём), а CPU-bound задачи ускоряют процессы (несколько ядер). Асинхронность не сделает быстрее перемножение матриц — там нечего «ждать», процессор и так загружен на 100%.

Имитация: ожидание против работы

Посчитаем на простой модели, почему перекрытие ожиданий выгодно. Пусть у нас три задачи, каждая «ждёт» 2 условные единицы. Синхронно — это 6 единиц, с перекрытием — около 2.

tasks = [("Запрос A", 2), ("Запрос B", 2), ("Запрос C", 2)]

# Синхронно: ожидания складываются
sync_time = sum(wait for _, wait in tasks)

# Асинхронно (I/O): ожидания перекрываются, итог ~ самое долгое
async_time = max(wait for _, wait in tasks)

print("Синхронно, единиц времени:", sync_time)
print("Асинхронно, единиц времени:", async_time)
print("Ускорение в разах:", sync_time / async_time)

Вывод:

Синхронно, единиц времени: 6
Асинхронно, единиц времени: 2
Ускорение в разах: 3.0

Это упрощённая модель, но она передаёт суть: при I/O мы выигрываем ровно потому, что ожидания накладываются друг на друга. Для CPU-bound такая модель неверна — там работа реальная, а не ожидание, и перекрыть её на одном ядре нельзя.

Что не делает асинхронность

  • Не запускает код параллельно на нескольких ядрах (один поток, одно ядро).
  • Не ускоряет вычисления — только перекрывает ожидания.
  • Не делает код «волшебно быстрым»: если нет ожиданий, выигрыша нет.

Итог

  • Синхронный код ждёт каждую операцию по очереди; асинхронный перекрывает ожидания.
  • I/O-bound задачи (сеть, диск) — кандидаты на асинхронность и потоки.
  • CPU-bound задачи ускоряются процессами на нескольких ядрах, не асинхронностью.
Проверьте себя
1. Какой тип задачи лучше всего ускорится от асинхронности?
AПеремножение больших матриц
BШифрование файла
CЗагрузка 100 страниц по сети
DСортировка списка в памяти
2. Почему асинхронность не ускоряет CPU-bound вычисления?
AПотому что процессор и так занят работой, а не ожиданием
BПотому что asyncio работает только с сетью
CПотому что вычисления нельзя завернуть в функцию
DПотому что GIL отключает процессор
3. Что означает «I/O-bound задача»?
AЗадача, ограниченная объёмом оперативной памяти
BЗадача, время которой определяется ожиданием ввода-вывода
CЗадача, которую нельзя распараллелить
DЗадача только для процессов
Поддержать проект