Пакет sync

Каналы — не единственный инструмент. Для простых задач синхронизации стандартная библиотека предлагает пакет sync.

sync.WaitGroup

Ожидание завершения группы горутин:

var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        fmt.Println("готово:", n)
    }(i)
}

wg.Wait()

В Go 1.25+ можно короче: wg.Go(func() { ... }).

sync.Mutex

Защита разделяемых данных от гонок:

type Counter struct {
    mu sync.Mutex
    n  int
}

func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.n++
}

sync.RWMutex позволяет множеству читателей работать одновременно (RLock/RUnlock), а писателю — эксклюзивно.

sync.Once

Гарантирует однократное выполнение, например ленивую инициализацию:

var (
    once sync.Once
    cfg  *Config
)

func GetConfig() *Config {
    once.Do(func() {
        cfg = loadConfig()
    })
    return cfg
}

sync/atomic

Для простых счётчиков удобны атомарные типы:

var hits atomic.Int64

hits.Add(1)
fmt.Println(hits.Load())

Каналы или мьютексы?