> Какие примитивы синхронизации есть в стандартной библиотеке Go (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Lamoda
Стек: Go
> Пример ответа
Короткий ответ
В стандартной библиотеке Go есть три основных примитива синхронизации: sync.Mutex (и sync.RWMutex), sync.WaitGroup и sync.Once. Также к ним относятся sync.Cond, sync.Pool и sync.Map, хотя последние два - это скорее высокоуровневые структуры, а не чистые примитивы. Каналы (chan) тоже можно считать примитивом синхронизации, так как они обеспечивают передачу данных и синхронизацию между горутинами.
Подробное объяснение
Все примитивы синхронизации в Go находятся в пакете sync. Основные из них:
sync.Mutex- взаимное исключение для защиты критических секций. МетодыLock()иUnlock().sync.RWMutex- mutex с разделением на чтение и запись. Позволяет множеству читателей одновременно, но только одному писателю. МетодыRLock(),RUnlock(),Lock(),Unlock().sync.WaitGroup- ожидание завершения группы горутин. МетодыAdd(delta),Done(),Wait().sync.Once- гарантирует, что функция выполнится ровно один раз, даже при конкурентном вызове. МетодDo(f).sync.Cond- условная переменная для сигнализации между горутинами. МетодыWait(),Signal(),Broadcast().sync.Pool- пул временных объектов для переиспользования и снижения нагрузки на GC.sync.Map- конкурентно-безопасная map, оптимизированная для сценариев с разными паттернами доступа.
Каналы (chan) - отдельный механизм, встроенный в язык. Они поддерживают синхронизацию через операции отправки и получения. Буферизованные каналы позволяют передавать данные без блокировки до заполнения буфера, небуферизованные - синхронизируют отправителя и получателя.
Важный нюанс: sync.Mutex и другие примитивы из sync нельзя копировать после первого использования - это приведёт к неопределённому поведению. Компилятор Go (начиная с версии 1.20) может предупреждать об этом через go vet.
На практике
Выбор примитива зависит от сценария:
- Для защиты общих данных от гонок -
sync.Mutexилиsync.RWMutex(если чтений значительно больше, чем записей). - Для ожидания завершения параллельных задач -
sync.WaitGroup. - Для инициализации singleton или ленивой загрузки -
sync.Once. - Для координации по условию (например, producer-consumer с ожиданием данных) -
sync.Condили каналы. - Для кэширования и переиспользования объектов -
sync.Pool. - Для конкурентного доступа к map с частыми чтениями и редкими записями -
sync.Map(но в большинстве случаев лучше использовать обычную map + mutex).
Каналы предпочтительнее, когда нужно передавать данные между горутинами, а не просто синхронизировать доступ к памяти. Идиоматичный подход в Go - "не общайтесь через разделяемую память, разделяйте память через общение". Однако для простой защиты счётчика или флага mutex - более прямое и эффективное решение.
Пример кода
GOpackage mainimport ("fmt""sync")var (counter intmu sync.Mutex)func increment(wg *sync.WaitGroup) {defer wg.Done()mu.Lock()counter++mu.Unlock()}func main() {var wg sync.WaitGroupfor i := 0; i < 1000; i++ {wg.Add(1)go increment(&wg)}wg.Wait()fmt.Println("counter:", counter)}
Как отвечать на собеседовании
Начни с перечисления основных примитивов, затем кратко опиши назначение каждого. Упомяни, что каналы - это отдельный механизм, но тоже примитив синхронизации. Приведи пример использования sync.Mutex и sync.WaitGroup - это база, которую ожидают услышать. Если спросят про sync.Once или sync.Cond - объясни, когда они применяются. Не углубляйся в детали реализации, если не просят. Хорошо показать понимание trade-off между mutex и каналами: mutex - для защиты данных, каналы - для передачи данных и сигнализации.
Что проверяет интервьюер
Интервьюер оценивает:
- знание стандартной библиотеки и её возможностей;
- понимание различий между примитивами и сценариев их применения;
- умение выбирать правильный инструмент под задачу;
- понимание идиоматичного подхода Go к конкурентности;
- знание ограничений (например, невозможность копирования mutex, опасность deadlock).
Типичные ошибки
- Забывают упомянуть
sync.RWMutexилиsync.Once. - Путают
sync.WaitGroupсsync.Once- первое для ожидания группы, второе для однократного выполнения. - Считают, что каналы - это не примитив синхронизации, а только средство передачи данных.
- Не знают про
sync.Condиsync.Pool. - Предлагают использовать
sync.Mapтам, где достаточно обычной map с mutex - это часто избыточно. - Не упоминают, что mutex нельзя копировать после использования.
> Похожие задачи по Go
Как реализуется раздача заданий и синхронизация горутин в воркерпуле в Go
Для чего нужен type switch в Go
Как называются типы объектов, которые при модификации возвращают копию, а не изменяют исходный объект
Для чего нужен select без default кейса в Go
> Похожие задачи по backend
Как реализуется раздача заданий и синхронизация горутин в воркерпуле в Go
Для чего нужен type switch в Go
Как называются типы объектов, которые при модификации возвращают копию, а не изменяют исходный объект
Для чего нужен select без default кейса в Go
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью