Как отвечать: определение → как работает внутри → где ловушка → пример. Отвечайте вслух до чтения ответа — собеседование это устный жанр, и «понимаю про себя» не равно «объясняю словами».
1. Как устроен sync.Once? Что происходит при панике внутри Do?
sync.Once держит атомарный флаг и мьютекс: первая горутина берёт мьютекс, выполняет функцию и атомарно ставит флаг; остальные либо видят флаг и выходят, либо ждут мьютекса и после его освобождения видят готовый результат. Паника внутри Do тоже считается выполнением: флаг ставится, и следующие вызовы НЕ повторят функцию — это осознанное поведение, чтобы «упавшая» инициализация не маскировалась бесконечными повторами. Копирование Once по значению — баг: два Once будут выполнять функцию независимо.
2. WaitGroup: где ошибка в коде wg.Add(1) внутри горутины?
Add должен вызываться ДО запуска горутины, а не внутри неё. Если Add внутри: главный Wait может выполниться раньше, чем горутина успеет сделать Add — счётчик ещё ноль, Wait мгновенно выходит, а горутина потом стартует «в никуда». Правильный паттерн: wg.Add(1) перед go func, defer wg.Done() внутри. Для динамического числа горутин — отдельный wg.Add на каждую перед запуском. Ошибка-ловушка: Add после Wait в цикле — Wait пройдёт, пока счётчик между Add и Done равен нулю, и горутины потеряются.
3. RWMutex всегда лучше мьютекса для read-heavy нагрузок?
Нет. RWMutex дороже по накладным расходам: RLock берёт читательский лок и обновляет счётчики читателей — каждая операция стоит атомарных операций, даже если конкурентов нет. Для коротких критических секций и небольшого числа горутин обычный Mutex быстрее. RWMutex окупается, когда: чтение долгое (сериализация большой структуры), читателей много (десятки), писателей мало и они редкие. Ещё ловушка: RLock не «апгрейдится» до Lock — попытка взять Lock под RLock даёт дедлок. Практический ответ: измеряйте contention (mutex profile), а не предполагайте.
4. sync.Map — когда он быстрее map с мьютексом?
sync.Map оптимизирован под два сценария: (1) ключи пишутся один раз и читаются много раз (кэши настроек, реестры); (2) горутины работают с непересекающимися наборами ключей. Устройство: read-only слот без блокировки для чтения + dirty слот с мьютексом для записи; при большом числе записей dirty промотируется в read, и это дорогая операция. Для смешанной нагрузки map+Mutex быстрее: sync.Map платит за сложную структуру даже там, где хватит простого мьютекса. Правило: sync.Map — специализированный инструмент, а не замена map.
5. Что такое голодание писателей в RWMutex и как Go с ним борется?
Наивный RWMutex может позволить читателям захватывать лок бесконечно: писатель ждёт, пока читателей ноль, а читатели приходят постоянно. Go решает это блокировкой новых читателей при появлении ожидающего писателя: как только писатель встал в очередь, новые RLock ждут его. Аналогичная защита есть у Mutex в «разогретом» режиме. Это означает: длинные цепочки читателей не заблокируют писателя навсегда, но и паузы чтения при активной записи неизбежны — это компромисс, а не бесплатная параллельность.
6. Что произойдёт при двойном RUnlock или Unlock без Lock?
Паника рантайма: «sync: RUnlock of unlocked RWMutex» и аналогично для Mutex. Паники из sync-примитивов не перехватываются recover в соседних горутинах корректно для логики — программа завершится. Защита — дисциплина: RLock/Lock и Unlock/RUnlock в одном методе через defer, без условных путей, где Unlock вызывается дважды. Классический источник дублей — ранний return внутри условной ветки, где Unlock уже вызван ранее по коду.
7. sync.Cond — что это и когда без него не обойтись?
sync.Cond — условная переменная: горутины ждут сигнала «условие могло измениться» (Wait) и просыпаются от Signal/Broadcast. Требует мьютекс-пару и цикл проверки условия (Wait может проснуться ложно — спууриос-пробуждение; поэтому всегда for !condition { c.Wait() }). Почти всегда заменяется каналами: закрытие канала работает как Broadcast, отправка — как Signal. Cond остаётся для легаси и для задач, где нужен точный контроль очереди ожидания на мьютексе.
Прокачать тему в самоучителе
Читается быстро, запоминается практикой. Эти курсы закрывают тему с запуском кода прямо на странице:
- Продвинутые возможности языка Go — Продвинутый курс Go: замыкания, defer, горутины, каналы, контекст, интерфейсы, дженерики, рефлексия, тесты и JSON. 25 уроков с практикой в браузере.