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

Как работать с горутинами в Go?

1.0 Junior🔥 161 комментариев
#Конкурентность и горутины

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

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

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

Управление горутинами в Go

Горутины — это легковесные потоки выполнения, реализованные в рантайме Go. Они позволяют выполнять функции конкурентно, что является фундаментальной концепцией языка для построения высокопроизводительных систем.

Создание и запуск горутин

Для запуска горутины используется ключевое слово go перед вызовом функции:

package main

import (
    "fmt"
    "time"
)

func printNumbers() {
    for i := 1; i <= 5; i++ {
        time.Sleep(250 * time.Millisecond)
        fmt.Printf("%d ", i)
    }
}

func main() {
    // Запуск горутины
    go printNumbers()
    
    // Основная горутина также продолжает работу
    for i := 'a'; i <= 'e'; i++ {
        time.Sleep(400 * time.Millisecond)
        fmt.Printf("%c ", i)
    }
    
    // Даем время на завершение горутины
    time.Sleep(2 * time.Second)
}

Синхронизация и обмен данными

Каналы (Channels)

Каналы — это типизированные конвейеры для связи между горутинами. Они обеспечивают безопасную передачу данных и синхронизацию:

func calculateSum(numbers []int, resultChan chan int) {
    sum := 0
    for _, num := range numbers {
        sum += num
    }
    resultChan <- sum // Отправка результата в канал
}

func main() {
    data := []int{1, 2, 3, 4, 5}
    resultChan := make(chan int) // Создание небуферизованного канала
    
    go calculateSum(data, resultChan)
    
    result := <-resultChan // Получение результата (блокирующая операция)
    fmt.Printf("Sum: %d\n", result)
    
    close(resultChan) // Закрытие канала
}

WaitGroup

sync.WaitGroup используется для ожидания завершения группы горутин:

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // Уменьшаем счетчик при завершении
    
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup
    
    for i := 1; i <= 3; i++ {
        wg.Add(1) // Увеличиваем счетчик
        go worker(i, &wg)
    }
    
    wg.Wait() // Ожидаем завершения всех горутин
    fmt.Println("All workers completed")
}

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

Worker Pool (Пул воркеров)

Этот паттерн ограничивает количество одновременно работающих горутин:

func workerPool(tasks <-chan int, results chan<- int, workerID int) {
    for task := range tasks {
        fmt.Printf("Worker %d processing task %d\n", workerID, task)
        results <- task * 2 // Пример обработки
    }
}

func main() {
    numWorkers := 3
    numTasks := 10
    
    tasks := make(chan int, numTasks)
    results := make(chan int, numTasks)
    
    // Запускаем воркеров
    for i := 1; i <= numWorkers; i++ {
        go workerPool(tasks, results, i)
    }
    
    // Отправляем задачи
    for i := 1; i <= numTasks; i++ {
        tasks <- i
    }
    close(tasks)
    
    // Собираем результаты
    for i := 1; i <= numTasks; i++ {
        fmt.Printf("Result: %d\n", <-results)
    }
}

Select для мультиплексирования каналов

Конструкция select позволяет обрабатывать несколько каналов одновременно:

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(2 * time.Second)
        ch1 <- "from ch1"
    }()
    
    go func() {
        time.Sleep(1 * time.Second)
        ch2 <- "from ch2"
    }()
    
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("Received:", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received:", msg2)
        case <-time.After(3 * time.Second):
            fmt.Println("Timeout")
            return
        }
    }
}

Важные практики и рекомендации

  • Всегда закрывайте каналы, когда в них больше не планируется отправлять данные, чтобы избежать deadlock
  • Используйте буферизованные каналы для уменьшения блокировок, но осторожно — они могут маскировать проблемы синхронизации
  • Контексты (context.Context) — используйте для отмены горутин и передачи метаданных
  • Избегайте утечек горутин — убедитесь, что все горутины могут завершиться
  • Обрабатывайте паники в горутинах с помощью defer и recover
  • Не полагайтесь на порядок выполнения — горутины выполняются конкурентно, не детерминировано

Распространенные ошибки

  1. Отправка в закрытый канал — вызывает панику
  2. Неиспользование WaitGroup или других механизмов синхронизации — приводит к преждевременному завершению программы
  3. Блокировка основной горутины — забывают сделать операцию чтения из канала
  4. Создание слишком большого количества горутин — хотя они легковесные, каждая потребляет память

Отладка и инструменты

  • pprof — профилирование горутин
  • trace — визуализация выполнения
  • race detector (go run -race) — обнаружение гонок данных
  • Горутины в состоянии deadlock — Go детектирует некоторые deadlock-ситуации в рантайме

Горутины — мощный инструмент, но требующий аккуратного обращения. Правильное использование каналов, примитивов синхронизации и контекстов позволяет создавать эффективные, безопасные и поддерживаемые конкурентные приложения на Go.

Как работать с горутинами в Go? | PrepBro