Ответ
В 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++
}
Когда что использовать:
- Каналы: Используйте, когда нужно передать владение данными от одной горутины к другой, для координации работы нескольких горутин или для потоковой передачи данных.
- Мьютексы: Используйте для защиты внутреннего состояния структуры или глобальных переменных от одновременного доступа. Часто это проще и производительнее для простых счетчиков или кешей.