Что такое Gorutine (горутина)?go-60

Что такое горутина?

Горутина — это легковесный поток выполнения в Go, который:

  • Не является потоком ОС (не 1:1 с kernel threads)
  • Имеет маленький стек (начинается с 2KB, динамически растет/сжимается)
  • Планируется runtime Go, а не операционной системой
  • Дешевле по стоимости создания и переключения (```200ns vs 1-10µs у потоков)

Ключевые характеристики

1. Легковесность

  • Можно создавать десятки тысяч горутин на обычной машине
  • Потребление памяти значительно ниже, чем у потоков ОС

Пример массового создания:

func main() {
    for i := 0; i < 100000; i++ {
        go func(num int) {
            fmt.Println(num)
        }(i)
    }
    time.Sleep(time.Second) // Даем время на выполнение
}

2. Простота использования

Запуск осуществляется через ключевое слово go:

go function(arg1, arg2)  // Асинхронный вызов функции

3. Интеграция с каналами

Горутины часто используют каналы для коммуникации:

func worker(jobs <-chan int, results chan<- int) {
    for j := range jobs {
        results <- j * 2
    }
}

Как работают горутины внутри?

Архитектура планировщика

  • M (Machine) — поток ОС (1:1 с kernel threads)
  • P (Processor) — логический процессор (количество = GOMAXPROCS)
  • G (Goroutine) — собственно горутина
┌───────────┐    ┌───────────┐
│   P (P1)  │    │   P (P2)  │
├─────┬─────┤    ├─────┬─────┤
│ G1  │ G2  │    │ G3  │ G4  │
└─────┴─────┘    └─────┴─────┘
     │                │
     ▼                ▼
┌───────────┐    ┌───────────┐
│   M (T1)  │    │   M (T2)  │
└───────────┘    └───────────┘

Состояния горутины

  1. Runnable — готова к выполнению
  2. Executing — выполняется на P
  3. Waiting — ожидает (канал, системный вызов и т.д.)

Практические аспекты

1. Синхронизация

Используйте:

  • Каналы (chan)
  • sync.WaitGroup
  • sync.Mutex/RWMutex для shared memory

Пример с WaitGroup:

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(num int) {
            defer wg.Done()
            fmt.Println(num)
        }(i)
    }

    wg.Wait() // Ожидаем завершения всех горутин
}

2. Утечки горутин

Типичные причины:

  • Забытый канал без читателя
  • Бесконечный цикл
  • Блокировка на мьютексе

3. Лучшие практики

  1. Всегда закрывайте каналы при завершении
  2. Используйте context.Context для отмены
  3. Ограничивайте количество параллельных операций (semaphore pattern)
  4. Избегайте блокирующих операций в горутинах

Отличия от потоков ОС

Характеристика Горутина Поток ОС
Стоимость создания 200-700ns 1-10µs
Память (стартовая) 2KB 1-8MB
Планировщик Go runtime ОС
Переключение контекста Дешевое (100ns) Дорогое (1µs)

Пример реального использования

func processConcurrently(urls []string) []Result {
    results := make([]Result, len(urls))
    sem := make(chan struct{}, 10) // Ограничение 10 параллельных запросов

    var wg sync.WaitGroup
    for i, url := range urls {
        wg.Add(1)
        go func(idx int, u string) {
            sem <- struct{}{}        // Занимаем слот
            defer func() {
                <-sem                // Освобождаем слот
                wg.Done()
            }()

            // Выполняем работу
            results[idx] = fetchData(u)
        }(i, url)
    }

    wg.Wait()
    return results
}

Резюмируем

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