Как организовать безопасную передачу данных между горутинами?

Ответ

В Go существует два основных подхода для безопасного обмена данными между горутинами, но один из них является идиоматичным.

"Do not communicate by sharing memory; instead, share memory by communicating."
(Не общайтесь через разделяемую память; вместо этого, разделяйте память через общение.)

1. Каналы (Channels) — идиоматичный способ

Каналы — это основной и предпочтительный механизм для синхронизации и передачи данных между горутинами. Они обеспечивают безопасную передачу данных без явных блокировок.

  • Небуферизованный канал (make(chan int)): Гарантирует синхронизацию. Отправка блокируется, пока получатель не будет готов принять данные, и наоборот.
  • Буферизованный канал (make(chan int, 10)): Позволяет отправить несколько значений без блокировки, пока буфер не заполнится. Это может повысить производительность, но снижает синхронизацию.

Пример:

func main() {
    // Создаем небуферизованный канал для строк
    ch := make(chan string)

    go func() {
        // Отправляем данные в канал в другой горутине
        ch <- "Hello from goroutine!"
    }()

    // Получаем данные из канала в основной горутине
    // Эта операция заблокирует main, пока данные не поступят
    message := <-ch
    fmt.Println(message)

    // Закрывать канал нужно, когда отправка данных точно завершена
    close(ch)
}

2. Разделяемая память с мьютексами (sync.Mutex)

Это более традиционный подход, используемый в других языках. Доступ к общим данным защищается с помощью мьютекса, чтобы предотвратить состояние гонки (race condition).

Пример:

var counter int
var mu sync.Mutex

func increment() {
    mu.Lock()   // Блокируем доступ
    defer mu.Unlock() // Гарантируем разблокировку
    counter++
}

Когда что использовать:

  • Каналы: Используйте, когда нужно передать владение данными от одной горутины к другой, для координации работы нескольких горутин или для потоковой передачи данных.
  • Мьютексы: Используйте для защиты внутреннего состояния структуры или глобальных переменных от одновременного доступа. Часто это проще и производительнее для простых счетчиков или кешей.