> Можно ли шарить данные между потоками (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: Avito

Стек: Go

> Пример ответа

Короткий ответ

Да, в Go данные между потоками (горутинами) шарить можно, но идиоматичный подход - не разделять память, а коммуницировать через каналы: "Do not communicate by sharing memory; instead, share memory by communicating". Однако на практике разделяемая память тоже используется - через мьютексы, атомики или sync.Map. Главное - соблюдать правила синхронизации, чтобы избежать гонок данных (data races). Выбор зависит от сценария: каналы для потоков данных и событий, мьютексы для защиты критических секций, атомики для простых счётчиков.

Подробное объяснение

В Go "потоки" - это горутины, которые выполняются конкурентно на пуле OS-потоков. Шарить данные между ними можно двумя основными способами:

  1. Через каналы - передача владения данными. Горутина-отправитель передаёт значение, получатель забирает его. Это безопасно, потому что в каждый момент времени доступ к данным имеет только одна горутина. Каналы бывают буферизированные и небуферизированные, работают по принципу FIFO.

  2. Через разделяемую память с синхронизацией - несколько горутин читают и пишут в одну переменную, но доступ контролируется:

    • sync.Mutex / sync.RWMutex - для сложных структур и критических секций
    • sync/atomic - для простых числовых операций (счётчики, флаги)
    • sync.Map - для конкурентного чтения/записи в map (оптимизирован под сценарии, где ключи стабильны, а значения часто читаются)

Важно понимать trade-off: каналы дают более высокоуровневую абстракцию и упрощают рассуждение о корректности, но добавляют накладные расходы на копирование и синхронизацию. Мьютексы эффективнее для частого доступа к большим структурам, но требуют дисциплины и повышают риск deadlock'ов.

Go-рантайм предоставляет детектор гонок (-race), который помогает находить проблемы при тестировании. На уровне языка нет защиты от гонок - это ответственность разработчика.

На практике

Для senior-позиции важно показать понимание, когда какой инструмент выбирать:

  • Каналы - когда данные передаются от производителя к потребителю, есть поток событий, или нужно организовать pipeline. Особенно если логика естественно ложится на модель "отправил-получил".
  • Mutex - когда несколько горутин работают с одной структурой, и операции не сводятся к простому обмену сообщениями (например, обновление нескольких полей структуры).
  • Atomic - для счётчиков, флагов, прогресс-баров, где нужна максимальная производительность и нет сложной логики.
  • sync.Map - когда ключи в map почти не меняются, но значения читаются очень часто из многих горутин.

На практике часто комбинируют: например, канал для управления жизненным циклом, мьютекс для защиты состояния, атомики для статистики.

Пример кода

GO
// Пример с каналом - передача данных
func worker(ch <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for v := range ch {
fmt.Println("got:", v)
}
}
// Пример с мьютексом - защита счётчика
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
// Пример с атомиком - быстрый счётчик
var atomicCounter int64
atomic.AddInt64(&atomicCounter, 1)
val := atomic.LoadInt64(&atomicCounter)

Как отвечать на собеседовании

Начните с чёткого "да, можно", затем упомяните два подхода: каналы и разделяемую память с синхронизацией. Подчеркните, что идиоматичный путь - каналы, но на практике нужны все инструменты. Приведите конкретные сценарии: каналы для pipeline, мьютексы для сложных структур, атомики для счётчиков. Обязательно упомяните детектор гонок и правило "не разделяй память, а передавай данные". Если спросят про производительность - скажите, что атомики быстрее мьютексов, а каналы медленнее, но безопаснее по дизайну. Покажите, что понимаете trade-off между простотой и скоростью.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимание модели конкурентности в Go (горутины, а не потоки OS)
  • Знание идиом: каналы как основной механизм, а не просто "ещё один способ"
  • Умение выбирать инструмент под задачу, а не использовать один везде
  • Понимание проблем: data race, deadlock, starvation
  • Знание sync-пакета и atomic-операций
  • Практический опыт: когда что реально применяется в продакшене

Типичные ошибки

  • Ответ "да, через глобальные переменные" без упоминания синхронизации - сразу красный флаг
  • Утверждение, что каналы - единственный правильный способ, и мьютексы не нужны
  • Игнорирование детектора гонок и тестирования с -race
  • Непонимание разницы между буферизированным и небуферизированным каналом
  • Предложение использовать sync.Map для всех случаев - это узкоспециализированный инструмент
  • Забывают про sync.RWMutex для сценариев "много читателей, мало писателей"

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью