Какие знаешь примитивы синхронизации?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Примитивы синхронизации в Go
В пакете sync стандартной библиотеки Go содержатся мощные примитивы для координации между горутинами. Давайте рассмотрим все основные из них:
1. sync.Mutex (Mutual Exclusion)
Самый базовый примитив — взаимное исключение. Гарантирует, что только одна горутина может одновременно находиться в критической секции.
var mu sync.Mutex
var counter = 0
func increment() {
mu.Lock()
defer mu.Unlock() // важно!
counter++
}
Основное правило: всегда используй defer Unlock(), чтобы избежать deadlock'ов при паниках.
2. sync.RWMutex (Read-Write Mutex)
Оптимизирована для случаев, когда читателей много, а писателей мало. Несколько горутин могут одновременно читать, но писатель требует эксклюзивного доступа.
var rwmu sync.RWMutex
var data = make(map[string]string)
func read(key string) string {
rwmu.RLock()
defer rwmu.RUnlock()
return data[key]
}
func write(key, value string) {
rwmu.Lock()
defer rwmu.Unlock()
data[key] = value
}
3. sync.WaitGroup
Используется для ожидания завершения группы горутин. Очень полезна для координации.
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done() // сигнализирует об окончании
doWork(id)
}(i)
}
wg.Wait() // блокируется пока все горутины не завершат Work()
}
4. sync.Once
Гарантирует, что функция выполнится ровно один раз, даже если её вызовут из множества горутин одновременно. Идеально для инициализации.
var once sync.Once
var instance *Singleton
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{
// инициализация
}
})
return instance
}
5. sync.Cond (Condition Variable)
Позволяет горутинам ждать возникновения определённого условия. Один сигнал может разбудить одну горутину, Broadcast разбудит всех.
var cond = sync.NewCond(&sync.Mutex{})
var ready = false
func wait() {
cond.L.Lock()
for !ready { // важно использовать цикл!
cond.Wait() // освобождает lock и ждёт сигнала
}
cond.L.Unlock()
// обработка
}
func signal() {
cond.L.Lock()
ready = true
cond.Broadcast() // разбудить всех ждущих
cond.L.Unlock()
}
6. sync.Semaphore (через context, golang.org/x/sync/semaphore)
Семафор ограничивает количество горутин, которые могут одновременно получить доступ к ресурсу. Это как маршальница с определённым количеством мест.
import "golang.org/x/sync/semaphore"
var sem = semaphore.NewWeighted(int64(maxConcurrency))
func doWork(ctx context.Context) {
if err := sem.Acquire(ctx, 1); err != nil {
// не смогли получить место
return
}
defer sem.Release(1)
// работа
}
Channels — встроенный примитив синхронизации
Не входит в пакет sync, но это мощнейший инструмент синхронизации. Channels — это безопасный способ передачи данных между горутинами.
// Unbuffered channel — синхронизирует отправителя и получателя
ch := make(chan int)
go func() {
ch <- 42 // отправка
}()
value := <-ch // получение, блокируется пока нет значения
// Buffered channel — может хранить N значений
ch2 := make(chan int, 10)
for i := 0; i < 10; i++ {
ch2 <- i // не блокируется пока буфер не переполнен
}
// Select для множественных операций
select {
case msg := <-ch1:
fmt.Println("Got from ch1:", msg)
case msg := <-ch2:
fmt.Println("Got from ch2:", msg)
case <-time.After(time.Second):
fmt.Println("Timeout!")
}
Практическое сравнение
| Примитив | Использование | Пример |
|---|---|---|
| Mutex | Критические секции | Доступ к shared map |
| RWMutex | Много читателей | Cache с редкими обновлениями |
| WaitGroup | Ожидание завершения | Обработка batch задач |
| Once | Одноразовая инициализация | Singleton pattern |
| Cond | Ожидание события | Producer-Consumer |
| Semaphore | Ограничение concurrency | Connection pool |
| Channel | Передача данных и синхронизация | Pipeline, fan-out/fan-in |
Правила использования
- Prefer channels над mutex'ами для простых случаев — они более безопасны
- Не смешивай примитивы без необходимости — это усложняет отладку
- Всегда используй defer при unlock — protection от паник
- Избегай deadlock'ов — правильный порядок acquire/release
- Race detector твой друг: go test -race
Каждый примитив решает свою задачу. Выбирай в зависимости от паттерна использования.