Сравнение: Мьютексы и Каналы в Go
Go предлагает два подхода к синхронизации и обмену данными между горутинами:
- 🔒 Мьютексы (Mutex) — синхронизация доступа к общей памяти
 - 📬 Каналы (Channels) — передача данных без разделяемой памяти
 
🔒 Мьютексы (sync.Mutex)
Мьютекс позволяет гарантировать, что только одна горутина в момент времени имеет доступ к критической секции кода.
✅ Пример:
import "sync"
type Counter struct {
    m  sync.Mutex
    v  int
}
func (c *Counter) Inc() {
    c.m.Lock()
    c.v++
    c.m.Unlock()
}
🧠 Когда использовать мьютекс:
- Множественные горутины читают/пишут переменную
 - Работа с 
map,sliceбез гонки данных - Критический код, который нельзя выполнять одновременно
 
📬 Каналы (chan)
Каналы передают данные между горутинами. Это позволяет избежать прямого доступа к разделяемой памяти.
✅ Пример:
func worker(jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- job * 2
    }
}
func main() {
    jobs := make(chan int, 5)
    results := make(chan int, 5)
    go worker(jobs, results)
    jobs <- 1
    jobs <- 2
    close(jobs)
    fmt.Println(<-results)
    fmt.Println(<-results)
}
🧠 Когда использовать каналы:
- Потоковая обработка данных
 - Логика “продюсер/консюмер”
 - Синхронизация начала/окончания работы
 - Архитектура “fan-in”, “fan-out”
 
⚖️ Сравнение
| Критерий | Мьютексы | Каналы | 
|---|---|---|
| Принцип | Общая память | Сообщения | 
| Сложность | Низкая, но с рисками | Чище, но требует дизайна | 
Поддержка pprof | 
Да (через mutex) | 
Частично (горутины) | 
| Предотвращение гонок | Да | Да | 
| Применение | Быстрый доступ | Асинхронные операции | 
🧪 Рекомендация от Go-разработчиков
“Не общайся через общую память — делись памятью через общение.”
Используй каналы, если можешь выразить логику через них, и мьютексы, когда нужна низкоуровневая производительность.
📌 Итог
| Хочешь максимальную скорость | Используй sync.Mutex |
| Хочешь безопасный обмен | Используй chan |
| Хочешь лучшее из двух миров | Используй sync.Map, sync.Once, sync.WaitGroup при необходимости |
🧰 Дополнительные инструменты из sync
Go также предоставляет удобные примитивы для управления конкурентностью:
🔁 sync.Once — выполнить только один раз
Позволяет гарантировать, что определённый код будет выполнен только один раз, даже если вызывается из нескольких горутин.
var once sync.Once
func initConfig() {
    once.Do(func() {
        fmt.Println("Конфигурация инициализирована")
    })
}
Используется для ленивой инициализации, подключения к БД, загрузки конфигов.
👥 sync.WaitGroup — дождаться завершения горутин
Позволяет дождаться, пока все горутины завершатся.
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        fmt.Printf("Горутина %d завершена
", id)
    }(i)
}
wg.Wait()
fmt.Println("Все горутины завершились")
🗺️ sync.Map — потокобезопасная map
Альтернатива обычной map, не требующая ручной синхронизации.
var sm sync.Map
sm.Store("foo", 42)
val, ok := sm.Load("foo")
if ok {
    fmt.Println("Значение:", val)
}
sm.Range(func(key, value any) bool {
    fmt.Printf("%v = %v
", key, value)
    return true
})
Подходит для кэширования, счётчиков, безопасной общей памяти между горутинами.
📌 Вывод:
| Инструмент | Назначение | 
|---|---|
sync.Mutex | 
Защита разделяемых данных | 
sync.Once | 
Инициализация кода только один раз | 
sync.WaitGroup | 
Ожидание завершения группы горутин | 
sync.Map | 
Потокобезопасный ассоциативный массив |