Перейти к содержимому
На этой странице

Конкурентность Go

Все темы Go Developer

Почему CAS не замечает проблему ABA?

CAS сравнивает только текущее значение с ожидаемым. Если goroutine прочитала A, другая успела заменить A на B и вернуть A, первый CAS увидит прежние биты и ошибочно решит, что состояние не менялось. Это ABA. Решение добавляет к значению monotonically changing version или tag и выполняет CAS над парой, либо использует mutex или алгоритм reclamation, который не позволяет повторно использовать объект слишком рано. Один указатель особенно опасен при удалении и повторном выделении памяти. Конкретная упаковка указателя и версии должна соблюдать требования атомарности и выравнивания платформы.


Когда в Go нужен sync.Cond?

sync.Cond нужен, когда несколько goroutines ждут изменения общего предиката под Locker, а событие само не является передаваемым значением. Проверка всегда идёт в цикле: lock, for !condition { cond.Wait() }, работа, unlock. Wait атомарно освобождает lock и после пробуждения захватывает его снова. Signal будит одну ожидающую goroutine, Broadcast все; пробуждение не гарантирует, что условие всё ещё истинно. Для очереди работ или одноразового уведомления channel обычно проще и безопаснее. Cond полезен для сложного общего состояния с несколькими возможными изменениями.


Когда errgroup удобнее WaitGroup?

errgroup.Group удобен для набора связанных goroutines, где caller ждёт их все и хочет получить первую ненулевую ошибку. errgroup.WithContext даёт context, который отменяется при первой ошибке или завершении Wait, поэтому остальные задачи могут остановить I/O и вычисления. SetLimit ограничивает число активных функций, TryGo позволяет не блокировать добавление. WaitGroup только считает завершения и не переносит ошибку или отмену, зато подходит, когда эти механизмы не нужны. Отмена errgroup остаётся кооперативной: функция должна использовать полученный context, иначе Wait продолжит ждать её.


Как избежать deadlock при блокировке двух mutex?

Все места, где нужны два mutex, должны захватывать их в одном глобальном порядке, например по неизменяемому ID объекта, и освобождать в обратном. Критические секции делают короткими и не выполняют внутри неизвестный callback, сеть или channel send. Если порядок трудно доказать, модель меняют: один mutex защищает общий инвариант, данные принадлежат одной goroutine либо операция разбивается без одновременных locks. TryLock с retry часто превращает deadlock в livelock и требует обоснования. Диагностика опирается на goroutine dump, block profile и точное владение locks.


Как race detector находит data race в Go?

Race detector инструментирует обращения к памяти и синхронизацию, затем во время выполнения ищет конфликтующие доступы из разных goroutines, где хотя бы один является записью и нет установленного happens-before. Запускают go test -race ./..., а также реальные сценарии через собранный с -race бинарник. Он видит только выполненные пути, поэтому успешный прогон не доказывает отсутствие races. Инструментация заметно увеличивает время и память. Отчёт содержит стеки конфликтующих доступов и создания goroutines; исправляют общее владение или синхронизацию, а не просто скрывают участок.


Чем data race отличается от race condition?

Data race в модели Go: два конкурентных доступа к одной области памяти, хотя бы один из них запись, без требуемой синхронизации. Это нарушение memory model, которое находит -race. Race condition шире: результат зависит от порядка событий, даже если каждый доступ синхронизирован. Например, две операции могут по отдельности безопасно проверить баланс и затем списать средства в неправильной последовательности под разными lock-секциями. Data race часто создаёт race condition, но множества не совпадают. Первую устраняют синхронизацией доступа; вторую, атомарностью бизнес-операции, протоколом или явной моделью состояния.


Почему RWMutex может быть медленнее Mutex?

RWMutex добавляет учёт читателей и более сложную координацию. Если критические секции короткие, конкуренция мала, writes регулярны или protected data плохо помещается в cache, этот overhead может быть выше выигрыша от параллельных reads. Долгий reader задерживает writer, а ожидающий writer влияет на новых readers по правилам реализации. Поэтому «чтений больше» недостаточно. Сначала используют простой Mutex, затем benchmark реального соотношения операций, длительности секций и числа goroutines. Иногда copy-on-write, atomic snapshot или разделение данных дают лучшее масштабирование, но усложнение оправдывают профилем.


Чем livelock отличается от deadlock?

При deadlock goroutines заблокированы и не могут сделать следующий шаг. При livelock они активны и постоянно реагируют друг на друга, но полезный прогресс отсутствует. Пример: два worker одновременно уступают один ресурс, повторяют проверку в одинаковом ритме и снова сталкиваются. CPU и логи могут быть активны, что отличает симптом от deadlock. Исправляют протокол владения, вводят ограниченное число попыток, случайный jitter или централизованную координацию. Один backoff без асимметрии может лишь замедлить livelock. Метрика должна отслеживать завершённую работу, а не число итераций цикла.


Что такое starvation в конкурентной программе?

Starvation означает, что конкретная goroutine долго не получает CPU, lock, очередь или другой ресурс, хотя система в целом продолжает работать. Причины: длинная критическая секция, постоянный поток конкурентов, приоритетная очередь без aging или busy loop. Симптомы видны как большой tail latency и редкие зависшие задачи при нормальном throughput. Уменьшают время удержания lock, ограничивают конкуренцию, делают очереди справедливее и добавляют backpressure. Нельзя полагаться на недокументированную fairness mutex или scheduler. Прогресс проверяют per-request метриками и стресс-тестом, а не только отсутствием deadlock.


Зачем ограничивать параллелизм через worker pool?

Worker pool ограничивает число одновременно выполняемых дорогих операций и тем самым защищает CPU, память, соединения и downstream. Producer отправляет задачи в bounded channel, фиксированное число workers читает их и возвращает результаты или ошибки. Заполненная очередь создаёт backpressure; политика должна явно решить, ждать, отклонять или сбрасывать задачу. Закрывает channel только producer, workers завершают range, а context останавливает приём и I/O. Ошибки удобно собирать через errgroup, но cancellation должна быть кооперативной. Размер pool выбирают измерением ресурса, а не числом goroutines «на глаз».

Собеседования: Go

Смотри записи интервью, узнай, какие вопросы задают и как отвечают кандидаты.

Вопросы и ответы

Не нашли ответ? Напишите мне в чат. Я делаю Шпаргалку и сам отвечаю на сообщения. Расскажите, что не работает или чего вам не хватает. Может, смогу сразу взять это в работу.

Откуда взяты вопросы?

Из реальных собеседований. Основой подборки стал опыт Вадима Новосёлова: он проходил интервью и записывал вопросы. Подробнее о материалах.

Насколько эти вопросы актуальны?

Эти вопросы встречались нам на реальных собеседованиях в 2025 году. Мы регулярно проходим собеседования и пополняем подборку новыми вопросами. Основы профессии и ключевые технологии остаются востребованными годами, а детали конкретных инструментов и версий стоит сверять с текущей документацией.

На какой уровень рассчитана подборка?

Мы проходили собеседования на вакансии уровня Middle+, а иногда и на Senior-позиции. Вопросы из этих интервью вошли в подборку. Направления работы: Go-разработчик, Backend-разработчик на Go. Глубина обсуждения зависит от вакансии: будь готов объяснить основную идею, привести практический пример и разобрать ограничения и альтернативы решения.

Этот вопрос точно будет на моём собеседовании?

Гарантии нет: набор вопросов зависит от компании, задач команды, уровня вакансии и самого интервьюера. Эти вопросы уже встречались на реальных собеседованиях, но на твоём интервью ту же тему могут проверить другой формулировкой, практической задачей или обсуждением твоего опыта. Используй подборку, чтобы разобраться в теме: объясняй идею своими словами, приводи примеры и готовься обсудить ограничения и альтернативы решения. Так будет проще ответить и на знакомый вопрос, и на неожиданные уточнения.