В каких случаях синхронность лучше асинхронности и почему?

Рейтинг: 5Ответов: 4Опубликовано: 22.04.2015

Асинхронные вещи с event loop по типу node.js или python tornado, как правило, используют в случае, когда нужно работать с кучей блокирующих операций - обращения к сети, загрузка файлов на сервер, сокеты и т.д. Но почему в других случаях используются синхронные решения?
К вопросу меня приводит примерно такой ход мысли:
- нам нужна асинхронность (явные случаи, описанные выше)? - пишем на чем-то асинхронном, здесь все очевидно.
- нам не нужна асинхронность? - вроде как асинхронный event loop не должен мешать в этом случае. почему не писать все на асинхронных технологиях?
Подозреваю, что у синхронных технологий есть свои преимущества перед асинхронными в случае, например, написания новостного сайта, но я их просто пока не понимаю.

Собственно, вопрос заключается в следующем: в каких случаях синхронность лучше асинхронности (конкретно event loop) и почему?

Ответы

▲ 5

Основная проблема с асинхронностью - это организация взаимодействия между потоками. Каждый поток в результате чего-то где-то генерирует - некий выхлоп, результат. Этот выхлоп сам по себе неинтересен, он интересен только тогда когда этот результат может быть где-то применен.

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

Поэтому асинхронность имеет смысл для задач когда потоки взаимодействуют друг с другом в строго изолированных местах: типа есть начало потока (начальные данные), сам поток где что-то вычисляется, далее окончание потока где от потока получаем результат. В противном случае получается каша где нужно непрерывно синхронизировать потоки, останавливать, ждать, джойнить и проч. семафорно-мьютексные истории.

▲ 2

Я бы ответил так:

  • Асинхронность лучше там, где подразумевается много IO(input-output)
  • Синхронность лучше там, где требуется производить "тяжелые" операции

Немного подробнее: давайте попробуем взглянуть на банальный сервер, который читает команды клиента и что-то отвечает клиенту.

Асинхронный путь выглядит в данном случае так:

  1. заводим event loop, ставящий задачи в очередь;
  2. дергаем указанный callback, когда какая-то задача выполнилась;
  3. обрабатываем этот event loop в один или несколько потоков.

Итак, представим себе, что callback - очень "тяжелая" функция, производит мощные расчеты и выполняется, например, секунду. Так вот, если наш event loop обрабатывается только одним потоком, и на сервер пришел один клиент, то в течение секунды наш сервер не сможет принимать входящие сообщения от других клиентов, пока event loop занят вычислением callback. Если event loop обрабатывается в 2 потока, то в таком случае два параллельно подключившихся клиента уже делают невозможным (на секунду) подключение третьего клиента. И так далее.

Эта проблема, в принципе, имеет решение. Можно "тяжелые" задачи тоже выполнять асинхронно, но для этого потребуется еще один пул потоков, специально под эти "тяжелые" задачи, и потребуется наладить взаимодействие между двумя пулами потоков. А это уже - не так-то просто и, насколько я знаю, прямо из коробки такого функционала нету в асинхронных фреймворках.
Кроме того, с числом потоков тут тоже надо не переборщить, иначе можно получить серьезную деградацию производительности.

На этом же примере видно преимущество синхронного пути. Мы, например, просто форкаем сервер для каждого нового клиента (самое простое), и в этом процессе-форке синхронно читаем команду, потом выполняем вычисления, потом отвечаем клиенту и завершаем работу. Никаких дополнительных сложностей не возникает..по крайней мере до тех пор, пока нам будет хватать памяти на сервере, чтобы держать в памяти копию сервера на каждого клиента :)

PS: Надеюсь, сущности типа callback и event loop не вызывают вопросов - это, в общем, основы асинхронного программирования, и я не расписывал подробно, что это. В некоторых фреймворках это может быть настолько глубоко спрятано, что может вызывать ложные представления о работе программы.

▲ 1

Каждый асинхронный вызов - это отдельный поток, плюс какие-то дополнительные сущности (мьютектсы, семафоры...), плюс усложнение логики работы вашей программы. Даже, если, конкретный фреймворк от вас это все скрывает, внутри это все равно есть. Мое мнение, что не надо вырезать гланды через задний проход, а если возможно писать проще - пишите проще.

▲ 1

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

Хорошая поддержка асинхронности - это когда 1. код не похож на лапшу, 2. при появлении исключительной ситуации стек вызовов не теряется, 3. отладчик не спотыкается на асинхронных вызовах, 4. используемые библиотеки не против асинхронного кода.

Если все условия выполняются - нет никаких причин использовать синхронные вызовы, даже если задача позволяет. Если что-то не так - то синхронный код всегда будет проще асинхронного.