Какие основные механизмы для коммуникации и синхронизации горутин существуют в Go?

Ответ

В 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. Используется в специфичных случаях, когда ключ добавляется один раз, а затем происходит множество параллельных чтений.