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

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

1.8 Middle🔥 212 комментариев
#Конкурентность и горутины

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

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

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

Взаимодействие горутин в Go

В Go горутины взаимодействуют между собой с помощью механизмов синхронизации и коммуникации, предоставляемых языком. Эти механизмы позволяют безопасно обмениваться данными и координировать выполнение параллельных задач, предотвращая race conditions и обеспечивая корректность программы.

Основные способы взаимодействия

1. Каналы (Channels)

Каналы — это типобезопасные каналы связи типа «первым вошел — первым вышел» (FIFO), которые позволяют горутинам отправлять и получать данные. Это основной и идиоматический способ взаимодействия в Go.

// Пример: обмен данными через небуферизованный канал
ch := make(chan int)

// Горутина-отправитель
go func() {
    ch <- 42 // Отправка значения
}()

// Горутина-получатель
value := <-ch // Получение значения

Особенности каналов:

  • Небуферизованные каналы (make(chan T)) обеспечивают синхронный обмен: отправка блокируется до тех пор, пока другая горутина не получит данные.
  • Буферизованные каналы (make(chan T, capacity)) позволяют отправлять несколько значений без немедленного получения, пока буфер не заполнится.
  • Каналы можно закрывать с помощью close(ch), что уведомляет получателей о завершении передачи данных.
  • Оператор select позволяет ждать операций с несколькими каналами.

2. Синхронизация через пакет sync

Пакет sync предоставляет примитивы низкоуровневой синхронизации.

var mu sync.Mutex
var sharedResource int

// Горутина 1
go func() {
    mu.Lock()
    sharedResource = 100
    mu.Unlock()
}()

// Горутина 2
go func() {
    mu.Lock()
    fmt.Println(sharedResource)
    mu.Unlock()
}()

Ключевые примитивы:

  • Mutex (sync.Mutex) и RWMutex (sync.RWMutex) для эксклюзивного доступа к общим данным.
  • WaitGroup (sync.WaitGroup) для ожидания завершения группы горутин.
  • Once (sync.Once) для гарантированного однократного выполнения кода.
  • Cond (sync.Cond) для организации более сложных сценариев ожидания.

3. Контекст (Context)

Пакет context позволяет передавать сигналы отмены, дедлайны и другие значения вниз по цепочке вызовов горутин.

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

go func(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("Операция отменена")
        return
    case <-time.After(3 * time.Second):
        fmt.Println("Операция завершена")
    }
}(ctx)

4. Атомарные операции (Atomic)

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

var counter int32

// Горутина 1
go func() {
    atomic.AddInt32(&counter, 1)
}()

// Горутина 2
go func() {
    atomic.AddInt32(&counter, 1)
}()

Паттерны взаимодействия горутин

  1. Worker Pool — пул рабочих горутин, обрабатывающих задачи из общего канала.
  2. Fan-out/Fan-in — распределение задач между несколькими горутинами (fan-out) и сбор результатов (fan-in).
  3. Pub/Sub — использование каналов для реализации подписки на события.
  4. Pipeline — цепочка обработки данных, где каждая стадия выполняется в отдельной горутине.

Пример: Worker Pool

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- job * 2 // Пример обработки
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    
    // Запуск 3 воркеров
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // Отправка задач
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)
    
    // Получение результатов
    for r := 1; r <= 5; r++ {
        <-results
    }
}

Ключевые принципы

  • «Не общайтесь, разделяя память; делитесь памятью, общаясь» — философия Go, предполагающая использование каналов вместо разделяемой памяти с мьютексами.
  • Каналы должны использоваться для передачи владения данными между горутинами, что уменьшает количество race conditions.
  • При выборе между каналами и мьютексами рекомендуется отдавать предпочтение каналам для высокоуровневых взаимодействий, а мьютексам — для критических секций в низкоуровневых библиотеках.

Правильное взаимодействие горутин требует понимания этих механизмов и их применения в соответствии с конкретными сценариями параллельной обработки.