> Какие средства синхронизации кроме мьютексов используются в Go (Go)
Уровень: senior · Роль: backend · Категория: Технические вопросы
Компании: Wildberries, Lamoda, Employcity
Стек: Go
> Пример ответа
Короткий ответ
В Go помимо мьютексов используются: атомарные операции (sync/atomic), каналы (channels) как основной механизм синхронизации горутин, sync.WaitGroup для ожидания завершения группы горутин, sync.Once для однократного выполнения, sync.Cond для условной синхронизации, sync.Pool для переиспользования объектов, а также примитивы из пакета context для отмены и дедлайнов. Выбор зависит от паттерна взаимодействия: каналы - для передачи данных и сигналов, атомарные операции - для простых счётчиков и флагов.
Подробное объяснение
Каналы - главный механизм синхронизации в Go, реализующий модель CSP (Communicating Sequential Processes). Они обеспечивают синхронизацию через передачу данных: отправка и получение блокируют горутины до завершения операции. Каналы бывают буферизированные и небуферизированные, что влияет на поведение блокировки.
sync.WaitGroup - счётчик ожидания завершения группы горутин. Методы Add, Done, Wait позволяют дождаться завершения всех задач без явной передачи сигналов через каналы.
sync.Once гарантирует, что функция выполнится ровно один раз, даже при конкурентном вызове. Используется для ленивой инициализации синглтонов.
sync.Cond - условная переменная, позволяющая горутинам ждать наступления определённого условия. Работает в связке с мьютексом, методы Wait, Signal, Broadcast.
sync/atomic - атомарные операции над примитивными типами: AddInt64, CompareAndSwap, Load, Store. Подходят для счётчиков, флагов, указателей без блокировок.
context.Context - механизм отмены и передачи дедлайнов между горутинами. Позволяет каскадно завершать работу при отмене родительского контекста.
sync.Pool - пул переиспользуемых объектов, снижает нагрузку на GC. Синхронизация через внутренние механизмы, не требует явных блокировок.
На практике
Для счётчика запросов используйте atomic.AddInt64, а не мьютекс - это быстрее и проще. Для координации worker-горутин - каналы: они делают поток данных явным. WaitGroup подходит для ожидания завершения пакетной обработки. Once - для инициализации конфигурации. Cond применяется редко, обычно когда нужна сложная логика ожидания с несколькими условиями. Context обязателен для любого долгоживущего процесса: HTTP-серверы, background-задачи.
Пример кода
GO// Атомарный счётчикvar counter int64atomic.AddInt64(&counter, 1)value := atomic.LoadInt64(&counter)// WaitGroupvar wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func() {defer wg.Done()// работа}()}wg.Wait()// Каналы для синхронизацииdone := make(chan struct{})go func() {// работаclose(done)}()<-done// Oncevar once sync.Onceonce.Do(func() { /* инициализация */ })// Contextctx, cancel := context.WithCancel(context.Background())go worker(ctx)cancel() // сигнал отмены
Как отвечать на собеседовании
Начните с каналов как основного инструмента, затем перечислите остальные примитивы. Для каждого кратко объясните назначение и типичный сценарий. Подчеркните trade-off: каналы - для передачи данных и событий, атомарные операции - для простых счётчиков, WaitGroup - для ожидания завершения. Упомяните, что мьютексы остаются для защиты критических секций с общим состоянием. Если спросят про производительность, скажите, что атомарные операции быстрее мьютексов, но ограничены примитивными типами.
Что проверяет интервьюер
Понимание модели конкурентности Go, умение выбирать правильный инструмент под задачу, знание стандартной библиотеки. Senior-уровень предполагает не просто перечисление, а объяснение, когда какой примитив уместен, и понимание внутренних механизмов (например, как каналы реализованы через мьютексы и условные переменные).
Типичные ошибки
- Использование мьютекса там, где достаточно атомарной операции.
- Передача данных через общую память с мьютексом вместо каналов - против идиомы Go.
- Забывание вызвать Done в WaitGroup при панике - используйте defer.
- Закрытие канала дважды или запись в закрытый канал - паника.
- Использование Cond без чёткого понимания, зачем он нужен - чаще всего можно обойтись каналами.
- Игнорирование context при работе с внешними вызовами - утечка горутин.
> Похожие задачи по backend
Какие примитивы синхронизации существуют кроме каналов в Go
Как планировщик Go распределяет goroutine между потоками
В чем разница между EXPLAIN и EXPLAIN ANALYZE
Какой опыт работы с Kubernetes и какие задачи выполнял
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью