← Назад к вопросам

Какие знаешь примитивы работы с горутинами?

2.0 Middle🔥 151 комментариев
#Конкурентность и горутины#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Примитивы работы с горутинами в Go

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

Базовые механизмы языка

Горутины (goroutines) — это легковесные потоки выполнения, создаваемые с помощью ключевого слова go. Они фундаментальный примитив, но для координации между ними нужны дополнительные средства.

go func() {
    fmt.Println("Выполняется в горутине")
}()

Каналы (channels) — типизированные конвейеры для связи между горутинами. Бывают буферизированные и небуферизированные.

ch := make(chan int, 5) // Буферизированный канал на 5 элементов
ch := make(chan int)    // Небуферизированный канал

Примитивы синхронизации из пакета sync

WaitGroup позволяет дождаться завершения группы горутин. Используется для простых сценариев "разделяй и властвуй".

var wg sync.WaitGroup
for i := 0; i < 10; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        // работа
    }(i)
}
wg.Wait()

Mutex (мьютекс) и RWMutex (read-write мьютекс) защищают общие данные от одновременного доступа.

var mu sync.Mutex
var counter int

mu.Lock()
counter++ // Критическая секция
mu.Unlock()

RWMutex более эффективен при частом чтении:

var rwMu sync.RWMutex
rwMu.RLock()   // Множественное чтение
// чтение данных
rwMu.RUnlock()

Once гарантирует однократное выполнение кода, даже из разных горутин.

var once sync.Once
initFunc := func() { fmt.Println("Инициализация") }
go once.Do(initFunc)
go once.Do(initFunc) // Выполнится только один раз

Cond (условные переменные) — механизм для ожидания событий, но в современном Go используется редко в пользу каналов.

Pool (пул объектов) для переиспользования дорогих объектов.

Расширенные примитивы и паттерны

Select — оператор для мультиплексирования каналов, похожий на switch для каналов.

select {
case msg := <-ch1:
    fmt.Println("Получено из ch1:", msg)
case ch2 <- data:
    fmt.Println("Отправлено в ch2")
case <-time.After(1 * time.Second):
    fmt.Println("Таймаут")
}

Context для отмены операций, таймаутов и передачи значений через границы горутин.

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

go func(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("Отменено:", ctx.Err())
    }
}(ctx)

Atomic операции из sync/atomic для низкоуровневых атомарных операций без мьютексов.

var val int32
atomic.AddInt32(&val, 1) // Атомарное увеличение

Практические паттерны и рекомендации

  1. Worker pools — пул воркеров для ограничения параллелизма
  2. Fan-out/fan-in — распределение работы и сбор результатов
  3. Pipeline — цепочка обработки через каналы
  4. Graceful shutdown — корректное завершение с использованием контекстов

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

Важно помнить о гонках данных (data races), которые обнаруживаются с помощью go run -race, и о deadlock'ах, когда горутины бесконечно ждут друг друга. Правильное использование примитивов синхронизации — основа надежных конкурентных программ на Go.

На практике я комбинирую эти примитивы: каналы для коммуникации, WaitGroup для ожидания групп, Mutex для защиты внутренних структур данных, и Context для управления жизненным циклом. Каждый примитив имеет свою нишу, и их грамотное сочетание позволяет создавать эффективные и безопасные конкурентные системы.

Какие знаешь примитивы работы с горутинами? | PrepBro