Уроки модуля (4/5)
`sync.Mutex` и `sync.WaitGroup`
Каналы — основная идиома Го, но иногда они более традиционны.
нужны примитивы. Пакет sync предоставляет мьютексы, подождите.
группы и другие утилиты для координации общего состояния.
sync.Mutex: эксклюзивная блокировка
import "sync"
type Counter struct {
mu sync.Mutex
count int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.count++
}
func (c *Counter) Get() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}Между Lock() и Unlock() одновременно может находиться только одна горутина.
Обязательный шаблон: defer mu.Unlock() сразу после Lock().
sync.RWMutex: много читателей, один писатель
Для состояния тяжелого чтения:
var mu sync.RWMutex
mu.RLock() // read lock: più goroutine simultanee OK
v := data
mu.RUnlock()
mu.Lock() // write lock: esclusivo
data = newValue
mu.Unlock()Полезно только в том случае, если чтение действительно доминирует над записью; в остальном обычный
sync.Mutex проще и зачастую быстрее благодаря меньшим внутренним
раздор.
sync.WaitGroup: дождаться N горутин
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
work(id)
}(i)
}
wg.Wait() // blocca finché tutti i Done() sono arrivatiРабочий процесс:
wg.Add(N)увеличивает счетчик на N (перед запуском горутин).- Каждая горутина вызывает
wg.Done()после завершения (в идеале черезdefer). wg.Wait()блокируется до тех пор, пока счетчик не вернется к 0.
sync.Once: потокобезопасная ленивая инициализация
var (
once sync.Once
config *Config
)
func GetConfig() *Config {
once.Do(func() {
config = loadConfig()
})
return config
}Обратный вызов в once.Do выполняется ровно один раз, даже при одновременном
звонки. Шаблон синглтона/ленивой инициализации.
Мьютекс против канала: когда что использовать
Рекомендации (также из официального FAQ по Go):
| Дело | Предпочтительный инструмент |
|---|---|
| Передача права собственности на данные | канал |
| Распределение работы (очередь работ) | канал |
| Координация независимых горутин | канал |
| Защита поля структуры | Мьютекс |
| Кэш/счетчик | Мьютекс или атомарный |
| Ссылка на синглтон | sync.Once |
«Каналы для оркестрации, мьютексы для общих данных» — хорошее практическое правило.
Детектор гонок
Запустите тесты с -race, чтобы обнаружить гонки данных:
go test -race ./...
go run -race main.goДетектор гонок анализирует двоичный файл и регистрирует все несинхронизированные доступ к общей памяти. Это инструмент №1 для проверки параллельного кода. перед производством.
Попробуйте это
Защитите приращение счетчика с помощью mu.Lock() + defer mu.Unlock().
Показать подсказку
defer mu.Unlock() сразу после Lock() гарантирует освобождение даже в случае паники.
Решение доступно после 3 попыток
Запустите 3 горутины и дождитесь их всех, используя sync.WaitGroup (Add(3), Done() в каждой горутине, Wait()).
Показать подсказку
Добавьте ПЕРЕД запуском горутины; Выполнено ВНУТРИ горутины с отсрочкой.
Решение доступно после 3 попыток
Какой рекомендуемый шаблон гарантирует освобождение мьютекса?
mu.Lock()
// ?Резюме
sync.Mutexзащищает общее состояние; всегдаdefer mu.Unlock().- Используйте приемник указателя, чтобы избежать копирования мьютекса.
sync.RWMutexдля шаблонов «много читателей/одна запись» (только если это помогает).sync.WaitGroup:Addпередgo,Doneсdefer,Waitждать.sync.Once: потокобезопасная ленивая инициализация.- «Каналы для оркестрации, мьютексы для общих данных».
go test -race: инструмент №1 для выявления конфликтов данных.