Вопрос проверяет знание базовых механизмов синхронизации горутин в Go, необходимых для написания безопасного конкурентного кода.
Go предоставляет богатый набор инструментов для синхронизации горутин, каждый из которых решает свою задачу. Основные примитивы включают мьютексы, каналы, WaitGroup, Once, Cond и атомарные операции.
Мьютекс (Mutex) — это блокировка, которая позволяет только одной горутине одновременно выполнять критическую секцию кода. RWMutex (Read-Write Mutex) оптимизирован для сценариев, где много читателей и мало писателей: несколько горутин могут одновременно читать, но запись блокирует всех.
var mu sync.Mutex
var counter int
func increment() {
mu.Lock()
counter++
mu.Unlock()
}Каналы — это типизированные трубы для передачи данных между горутинами. Они могут быть буферизированными или небуферизированными. Небуферизированный канал блокирует отправителя до получения данных получателем, обеспечивая синхронизацию.
ch := make(chan int)
go func() {
ch <- 42
}()
value := <-ch // блокируется, пока не придет значениеsync.WaitGroup используется для ожидания завершения набора горутин. Вызов Add увеличивает счетчик, Done уменьшает, а Wait блокирует выполнение до обнуления счетчика.
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Println(id)
}(i)
}
wg.Wait()sync.Once гарантирует однократное выполнение функции, даже при вызове из нескольких горутин. sync.Cond позволяет горутинам ждать определенного условия и уведомлять друг друга о его наступлении.
Пакет atomic предоставляет низкоуровневые атомарные операции для работы с памятью без блокировок: Add, Load, Store, CompareAndSwap и другие. Они эффективнее мьютексов для простых счетчиков и флагов.
var counter int64
atomic.AddInt64(&counter, 1)Вывод: Выбор примитива зависит от задачи: для защиты общих данных используйте Mutex или RWMutex, для обмена данными — каналы, для ожидания группы горутин — WaitGroup, для однократных инициализаций — Once, для сложных условий — Cond, а для простых счетчиков — атомарные операции.