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

Для чего используют пакет sync?

1.0 Junior🔥 171 комментариев
#Конкурентность и горутины

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

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

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

Основное назначение пакета sync

Пакет sync в Go является фундаментальным инструментом для синхронизации доступа к общим ресурсам в многопоточных (concurrent) программах. Его главная цель — предоставить примитивы синхронизации, которые позволяют корректно управлять доступом горутин (goroutines) к разделяемым данным, предотвращая состояния гонки (race conditions), гонки данных (data races) и обеспечивая предсказуемое поведение программы.

В условиях, когда несколько горутин выполняются параллельно и обращаются к одним и тем же переменным или структурам, без синхронизации это приводит к неопределённым результатам, повреждению данных и трудноотлавливаемым ошибкам. Пакет sync решает эти проблемы, предлагая ряд готовых высокоуровневых примитивов.

Ключевые типы и их применение

1. sync.Mutex — базовый мьютекс

Наиболее часто используемый примитив. Предоставляет эксклюзивную блокировку (mutual exclusion). Только одна горутина может захватить мьютекс в данный момент времени.

package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu    sync.Mutex
    value int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()         // Захватываем мьютекс
    defer c.mu.Unlock() // Гарантируем освобождение
    c.value++
}

func (c *SafeCounter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

func main() {
    var wg sync.WaitGroup
    counter := SafeCounter{}

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println(counter.Value()) // Всегда 1000
}

2. sync.RWMutex — читающе-записывающий мьютекс

Оптимизирован для сценариев «много читателей, редко пишущий». Позволяет нескольким горутинам одновременно читать данные, но запись требует эксклюзивной блокировки.

type Config struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Config) Get(key string) string {
    c.mu.RLock()        // Блокировка для чтения (не экск-\nлюзивная)
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Config) Set(key, value string) {
    c.mu.Lock()         // Эксклюзивная блокировка для записи
    defer c.mu.Unlock()
    c.data[key] = value
}

3. sync.WaitGroup — ожидание завершения горутин

Позволяет основной горутине дождаться завершения набора рабочих горутин.

func processConcurrently(items []string) {
    var wg sync.WaitGroup
    
    for _, item := range items {
        wg.Add(1) // Увеличиваем счётчик перед запуском горутины
        go func(it string) {
            defer wg.Done() // Уменьшаем счётчик при завершении
            // Обработка item
            fmt.Println(it)
        }(item)
    }
    
    wg.Wait() // Блокируемся, пока счётчик не станет 0
    fmt.Println("Все горутины завершились")
}

4. sync.Once — однократное выполнение

Гарантирует, что определённая операция будет выполнена только один раз, даже если её вызывают из нескольких горутин одновременно.

var (
    initOnce sync.Once
    config   *Config
)

func GetConfig() *Config {
    initOnce.Do(func() {
        // Этот код выполнится ровно один раз
        config = loadConfigFromFile()
    })
    return config
}

5. sync.Map — потокобезопасная мапа

Специализированная карта, оптимизированная для двух сценариев:

  • Ключи не изменяются после записи
  • Много горутин читают/пишут в разные ключи
var m sync.Map

// Хранение
m.Store("key1", 42)

// Загрузка
if value, ok := m.Load("key1"); ok {
    fmt.Println(value)
}

// Атомарные операции
m.LoadOrStore("key2", 100)

6. sync.Pool — пул временных объектов

Позволяет кэшировать и переиспользовать временные объекты, уменьшая нагрузку на сборщик мусора.

var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func getBuffer() *bytes.Buffer {
    return bufferPool.Get().(*bytes.Buffer)
}

func putBuffer(buf *bytes.Buffer) {
    buf.Reset()
    bufferPool.Put(buf)
}

Когда использовать пакет sync

  • Защита разделяемых данных — когда несколько горутин модифицируют общие переменные
  • Координация горутин — организация их последовательного или параллельного выполнения
  • Реализация однократной инициализации — синглтоны, конфигурация
  • Управление пулами ресурсов — соединения с БД, буферы
  • Создание потокобезопасных структур данных — кэши, счётчики, очереди

Важные принципы использования

  • Всегда освобождайте мьютексы — используйте defer для гарантии
  • Избегайте вложенных блокировок — может привести к deadlock
  • Держите блокировки минимальное время — выполняйте только критическую секцию под мьютексом
  • Для частого чтения используйте RWMutex — он эффективнее обычного Mutex
  • sync.Map не замена обычной map с мьютексом — только для специфичных случаев

Пакет sync является краеугольным камнем для написания корректных конкурентных программ на Go, предоставляя проверенные временем примитивы, которые компенсируют отсутствие в языке традиционных потоков ОС и сложных механизмов синхронизации.