← Назад к вопросам

Какие знаешь примитивы синхронизации?

2.2 Middle🔥 281 комментариев
#Конкурентность и горутины#Основы Go

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Примитивы синхронизации в 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Ограничение concurrencyConnection pool
ChannelПередача данных и синхронизацияPipeline, fan-out/fan-in

Правила использования

  1. Prefer channels над mutex'ами для простых случаев — они более безопасны
  2. Не смешивай примитивы без необходимости — это усложняет отладку
  3. Всегда используй defer при unlock — protection от паник
  4. Избегай deadlock'ов — правильный порядок acquire/release
  5. Race detector твой друг: go test -race

Каждый примитив решает свою задачу. Выбирай в зависимости от паттерна использования.

Какие знаешь примитивы синхронизации? | PrepBro