Сравнение: Мьютексы и Каналы в 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 |
Потокобезопасный ассоциативный массив |