Какие типы mutex используешь?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Типы мьютексов в Go: от базовых до продвинутых
В Go существует несколько типов мьютексов, каждый из которых решает специфические задачи синхронизации. Вот основные из них, которые я регулярно использую в production-коде:
1. Стандартный sync.Mutex
Базовый взаимный исключитель (mutual exclusion), который предоставляет эксклюзивный доступ к общему ресурсу.
package main
import (
"fmt"
"sync"
"time"
)
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.v[key]++
}
func main() {
c := SafeCounter{v: make(map[string]int)}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
c.Inc("somekey")
}()
}
wg.Wait()
fmt.Println(c.v["somekey"]) // Всегда 1000
}
Ключевые особенности:
- Только одна горутина может захватить мьютекс в любой момент времени
- Использование
deferдля гарантированного разблокирования - При попытке повторного захвата той же горутиной приводит к deadlock
2. sync.RWMutex (Read-Write Mutex)
Оптимизированный мьютекс для сценариев "много читателей, мало писателей". Позволяет множественным горутинам читать данные одновременно, но эксклюзивно блокирует при записи.
type ConfigManager struct {
mu sync.RWMutex
config map[string]string
}
func (cm *ConfigManager) Get(key string) string {
cm.mu.RLock() // Блокировка для чтения (множественная)
defer cm.mu.RUnlock()
return cm.config[key]
}
func (cm *ConfigManager) Set(key, value string) {
cm.mu.Lock() // Эксклюзивная блокировка для записи
defer cm.mu.Unlock()
cm.config[key] = value
}
Преимущества RWMutex:
- Высокая производительность в read-heavy сценариях
- Минимальные блокировки при параллельном чтении
- Детерминированная синхронизация при обновлениях
3. Каналы как альтернатива мьютексам
Идиоматичный Go-подход "Don't communicate by sharing memory; share memory by communicating":
// Вместо мьютекса используем канал-мультиплексор
type Counter struct {
requests chan struct{}
counts chan int
}
func NewCounter() *Counter {
c := &Counter{
requests: make(chan struct{}),
counts: make(chan int),
}
go c.run()
return c
}
func (c *Counter) run() {
var count int
for {
select {
case <-c.requests:
count++
c.counts <- count
}
}
}
4. sync.Map для специфических случаев
Специализированная concurrent-мапа, использующая внутренние мьютексы оптимальным образом:
var m sync.Map
// Параллельные операции безопасны
m.Store("key", "value")
value, ok := m.Load("key")
m.Delete("key")
Когда использовать sync.Map:
- Когда ключи почти не обновляются, но часто читаются
- При работе с несколькими независимыми наборами ключей
- В кэширующих системах с редкими обновлениями
5. Атомарные операции sync/atomic
Для простых счетчиков и флагов атомарные операции эффективнее мьютексов:
import "sync/atomic"
type AtomicCounter struct {
value int64
}
func (c *AtomicCounter) Add(delta int64) {
atomic.AddInt64(&c.value, delta)
}
func (c *AtomicCounter) Value() int64 {
return atomic.LoadInt64(&c.value)
}
Критерии выбора типа синхронизации
-
sync.Mutexвыбираю когда:- Нужна простая эксклюзивная блокировка
- Операции записи частые
- Логика критической секции сложная
-
sync.RWMutexпредпочитаю для:- Конфигураций, кэшей, справочников
- Read-heavy workload (90%+ операций чтения)
- Данных, которые редко обновляются
-
Каналы использую когда:
- Нужна координация между горутинами
- Требуются таймауты или отмена операций
- Реализуется паттерн worker pool
-
Атомарные операции применяю для:
- Счетчиков, флагов состояния
- Простых числовых операций
- Когда производительность критична
Продвинутые практики
Graceful shutdown с мьютексами:
type Service struct {
mu sync.RWMutex
stopped bool
}
func (s *Service) Stop() {
s.mu.Lock()
defer s.mu.Unlock()
s.stopped = true
}
func (s *Service) Process() error {
s.mu.RLock()
defer s.mu.RUnlock()
if s.stopped {
return errors.New("service stopped")
}
// ... обработка
}
Важные нюансы:
- Всегда проверяю не происходит ли захват мьютекса в разном порядке (риск deadlock)
- Использую
deferдля Unlock везде, кроме оптимизированных hot-paths - Для отладки deadlock применяю
pprofиMutexProfile - В тестах использую
-raceфлаг для детекции гонок данных
Производительность: RWMutex может быть медленнее обычного Mutex при высоком contention или если операции в критической секции очень быстрые. Всегда измеряю производительность под нагрузкой, характерной для конкретного приложения.
Выбор типа мьютекса — это всегда компромисс между простотой, производительностью и безопасностью. Я начинаю с простого sync.Mutex, перехожу на RWMutex при доказанной необходимости, и рассматриваю каналы или атомарные операции для специфических паттернов доступа.