В Go существует несколько механизмов синхронизации данных между горутинами. Рассмотрим основные из них с примерами.
Каналы - это типизированные "трубы", через которые можно отправлять и получать значения между горутинами.
ch := make(chan int)
// Горутина-отправитель
go func() {
ch <- 42 // Отправка значения
}()
// Горутина-получатель
value := <-ch // Получение значения
ch := make(chan int, 2) // Буфер на 2 элемента
ch <- 1
ch <- 2
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
Мьютексы предоставляют эксклюзивный доступ к данным.
var mu sync.Mutex
var counter int
func increment() {
mu.Lock()
defer mu.Unlock()
counter++
}
Для оптимизации чтения/записи:
var rwMu sync.RWMutex
var data map[string]string
func readData(key string) string {
rwMu.RLock()
defer rwMu.RUnlock()
return data[key]
}
func writeData(key, value string) {
rwMu.Lock()
defer rwMu.Unlock()
data[key] = value
}
Для ожидания завершения группы горутин:
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// Работа горутины
}(i)
}
wg.Wait() // Ожидание всех горутин
Для атомарных операций с примитивами:
var counter int64
func increment() {
atomic.AddInt64(&counter, 1)
}
func getValue() int64 {
return atomic.LoadInt64(&counter)
}
Для однократного выполнения:
var once sync.Once
var config map[string]string
func loadConfig() {
once.Do(func() {
config = readConfigFile()
})
}
Для ожидания событий:
var mu sync.Mutex
var cond = sync.NewCond(&mu)
var ready bool
// Ожидающая горутина
mu.Lock()
for !ready {
cond.Wait()
}
mu.Unlock()
// Сигнализирующая горутина
mu.Lock()
ready = true
cond.Broadcast() // или cond.Signal()
mu.Unlock()
Для работы с несколькими каналами:
select {
case msg := <-ch1:
fmt.Println(msg)
case ch2 <- 42:
fmt.Println("sent")
case <-time.After(1 * time.Second):
fmt.Println("timeout")
default:
fmt.Println("no activity")
}
Метод | Когда использовать | Преимущества | Недостатки |
---|---|---|---|
Каналы | Передача данных, CSP-стиль | Чистый дизайн, безопасность | Может быть медленнее |
Мьютексы | Защита доступа к общим данным | Простота, производительность | Легко допустить deadlock |
Atomic | Простые счетчики/флаги | Максимальная производительность | Только примитивы |
WaitGroup | Ожидание группы горутин | Простота использования | Только для ожидания |
Once | Инициализация в одном месте | Гарантированное однократное выполнение | Ограниченный сценарий |
Основные способы синхронизации в Go:
Выбор метода зависит от конкретной задачи:
Правильная синхронизация - ключ к написанию корректных конкурентных программ на Go.