Синхронный и асинхронный код: зачем
Разбираемся, что значит «асинхронный», и когда он реально ускоряет программу, а когда нет.
Синхронный код выполняет операции строго по очереди: следующая строка ждёт, пока завершится предыдущая. Асинхронный код умеет переключаться на другую работу, пока текущая операция ждёт ответа извне.
Проблема ожидания
Представьте программу, которая загружает три страницы из интернета. Каждый запрос — это в основном ожидание: вы отправили запрос и сидите, пока придёт ответ. Процессор в это время простаивает. Синхронный код будет ждать первый ответ, потом второй, потом третий — суммарно три ожидания подряд.
Асинхронный подход позволяет отправить все три запроса и ждать их одновременно. Пока один запрос «висит», программа занимается другими. Время выполнения становится близким к одному ожиданию вместо трёх.
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 задачи ускоряются процессами на нескольких ядрах, не асинхронностью.