Ответ
В Go принята философия: "Не используйте разделяемую память для коммуникации; вместо этого, обменивайтесь данными через каналы". Основные механизмы делятся на две категории:
1. Коммуникация (Передача данных)
-
Каналы (
chan
) — это идиоматичный и основной способ безопасной передачи данных между горутинами. Канал представляет собой типизированную "трубу", в которую одна горутина может писать данные, а другая — читать.- Небуферизованные:
ch := make(chan int)
— запись блокируется, пока другая горутина не будет готова прочитать данные. - Буферизованные:
ch := make(chan int, 10)
— запись не блокируется, пока в буфере есть место.
ch := make(chan string) go func() { ch <- "Привет из горутины!" // Отправка данных в канал }() msg := <-ch // Чтение данных из канала fmt.Println(msg)
- Небуферизованные:
2. Синхронизация (Управление доступом и ожидание)
Используются примитивы из пакета sync
для координации работы горутин, когда необходимо использовать общую память.
-
sync.Mutex
/sync.RWMutex
— для защиты критических секций кода и предотвращения гонки данных при доступе к общим ресурсам.var mu sync.Mutex var counter int mu.Lock() // Блокируем доступ counter++ mu.Unlock() // Освобождаем
-
sync.WaitGroup
— для ожидания завершения работы группы горутин. Основной сценарий: главная горутина запускает несколько воркеров и ждет, пока все они закончат свою работу.var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) // Увеличиваем счетчик go func() { defer wg.Done() // Уменьшаем счетчик по завершении // ... какая-то работа }() } wg.Wait() // Ожидаем, пока счетчик не станет равен нулю
-
sync.Once
— гарантирует, что определенный участок кода будет выполнен только один раз, независимо от количества вызовов. -
sync.Cond
— более сложный примитив, позволяющий горутинам ожидать наступления определенного условия. -
sync.Map
— конкурентно-безопасная реализацияmap
. Используется в специфичных случаях, когда ключ добавляется один раз, а затем происходит множество параллельных чтений.