Ответ
Этот вопрос затрагивает ключевую философию конкурентности в Go: "Do not communicate by sharing memory; instead, share memory by communicating." (Не общайтесь через разделяемую память; вместо этого, разделяйте память через общение).
Каналы являются предпочтительным способом, потому что они обеспечивают безопасную и идиоматичную синхронизацию.
1. Каналы (Предпочтительный способ)
Каналы — это типизированные "трубы", через которые можно посылать и получать значения. Они обеспечивают три важные вещи:
- Безопасность (Memory Safety): Передача данных через канал гарантирует отсутствие гонок данных (data races). В любой момент времени только одна горутина имеет доступ к данным: либо та, что отправляет, либо та, что получает. Компилятор и рантайм следят за этим.
- Синхронизация: Отправка в небуферизованный канал блокирует горутину до тех пор, пока другая горутина не будет готова принять данные. И наоборот. Это делает синхронизацию явной и встроенной в сам процесс коммуникации.
- Четкое владение (Clear Ownership): Когда вы передаете указатель по каналу, вы неявно передаете и "владение" этими данными. Это помогает выстраивать более понятную и предсказуемую архитектуру.
Пример с каналом:
ch := make(chan int)
go func() {
// Горутина безопасно отправляет данные
ch <- 42
}()
// Основная горутина ждет и безопасно получает данные
value := <-ch
2. Общая память с мьютексами (Альтернативный способ)
Использование общей переменной, защищенной мьютексом (sync.Mutex
или sync.RWMutex
), — это традиционный подход из других языков.
- Проблемы: Легко допустить ошибку — забыть заблокировать мьютекс перед доступом к данным или разблокировать его после. Это приводит к гонкам данных или дедлокам. Код становится сложнее для анализа.
- Когда использовать: Этот подход оправдан для управления сложным состоянием или в сценариях, где требуется максимальная производительность и накладные расходы на каналы неприемлемы (например, простые счетчики, которые можно изменять с помощью пакета
sync/atomic
).
Пример с мьютексом:
var data int
var mu sync.Mutex
go func() {
mu.Lock() // Захватываем блокировку
data = 42 // Безопасно изменяем данные
mu.Unlock() // Освобождаем блокировку
}()
Вывод: Каналы делают код более читаемым, безопасным и соответствующим идиомам Go. Мьютексы — это мощный, но более низкоуровневый инструмент, который следует использовать осознанно, когда каналы не подходят.