Почему Go хорошо работает с асинхронными задачами?
Что проверяют
Знаете ли вы, что стоит за словом «горутина», и понимаете ли, что дешевизна запуска не отменяет ни гонок, ни утечек. Вопрос звучит как приглашение похвалить язык, а проверяет умение говорить про рантайм конкретно.
Как отвечать
Go делает не асинхронность в стиле обещаний, а дешёвую конкурентность с блокирующим кодом. Существенные детали:
- горутина это не поток операционной системы. Планировщик рантайма мультиплексирует множество горутин на небольшое число системных потоков, стек начинается с пары килобайт и растёт по мере необходимости, поэтому сотни тысяч горутин в одном процессе это норма;
- переключение происходит внутри рантайма, без перехода в ядро, и стоит заметно дешевле переключения контекста потока;
- сетевой ввод-вывод завязан на поллер: горутина, застрявшая на чтении из сокета, снимается с потока, а поток берёт другую работу. Код при этом остаётся линейным, без цепочек колбэков и без деления функций на обычные и асинхронные;
- каналы и
selectдают передачу данных и выбор между событиями,contextпротягивает отмену и крайний срок через всю цепочку вызовов.
Хорошо закончить границей применимости: конкурентность это не параллелизм. Счётную задачу ускоряет число ядер и GOMAXPROCS, а go перед вызовом сам по себе не ускоряет ничего.
Где спотыкаются
Говорят, что Go защищает от гонок данных. Не защищает: память общая, и гонка ловится только детектором в go test -race, да и то лишь та, которая случилась на конкретном прогоне. Каналы не отменяют sync.Mutex: для защиты одного поля мьютекс обычно проще и быстрее канала.
Второе: не помнят про утечку горутин. Горутина, которая пишет в канал без читателя или ждёт ответа, которого не будет, живёт до конца процесса и держит всю замкнутую память. У каждой запущенной горутины должен быть владелец и способ её остановить: отмена через context, закрытие канала, WaitGroup.
Третье: ставят go на каждый входящий запрос без ограничения. Горутины дешёвые, но пул соединений к базе и внешние сервисы нет, и неограниченное порождение просто переносит отказ на соседа. Нужна семафорная защита или рабочие горутины с очередью.