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

Что происходит при запуске горутины?

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

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

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

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

Механизм запуска горутины в Go

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

1. Инициализация и создание структуры данных

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

// Пример запуска горутины
go myFunction(arg1, arg2)

// Или анонимной функции
go func() {
    fmt.Println("Запущена горутина")
}()

На этом этапе происходит:

  • Выделение памяти для стека горутины (начальный размер обычно 2 КБ)
  • Создание структуры g (goroutine descriptor), которая содержит:
    • Указатель на стек
    • Информацию о текущем состоянии выполнения
    • Программный счетчик (PC) и указатель стека (SP)
    • Контекст планировщика
    • Каналы, с которыми связана горутина
    • Информацию для сборщика мусора

2. Подготовка контекста выполнения

Рантайм Go подготавливает контекст для выполнения функции:

  • Аргументы функции копируются в стек новой горутины
  • Локальные переменные инициализируются в стековом кадре
  • Замыкания (если используются) захватывают соответствующие переменные из окружающего контекста
  • Адрес возврата настраивается на специальную функцию завершения

3. Взаимодействие с планировщиком (scheduler)

Go использует M:N модель планирования, где M горутин выполняются на N потоках ОС (worker threads):

// Планировщик работает примерно так (упрощенно)
func schedule() {
    for {
        // Найти готовую к выполнению горутину
        gp := findRunnableGoroutine()
        
        // Выполнить горутину на текущем потоке ОС
        execute(gp)
        
        // При необходимости переключиться на другую горутину
        if needToSwitch() {
            contextSwitch()
        }
    }
}

Планировщик Go выполняет следующие действия:

  • Помещает новую горутину в очередь готовых к выполнению (runqueue)
  • Решает, на каком потоке ОС (M) будет выполняться горутина
  • Управляет переключением контекста между горутинами

4. Особенности управления памятью

При запуске горутины важно понимать работу со стеком:

  • Начальный размер стека — 2 КБ (может меняться в зависимости от версии Go)
  • Динамическое расширение/сжатие — стек может расти и сокращаться по мере необходимости
  • Разделенные стеки (segmented stacks) или непрерывные стеки с копированием в современных версиях Go

5. Сборка мусора и горутины

Рантайм Go отслеживает горутины для сборки мусора:

  • Каждая горутина содержит информацию для сборщика мусора
  • Точки безопасного останова (safe points) позволяют остановить горутину для сборки мусора
  • Пишет барьеры (write barriers) обеспечивают корректность при изменении указателей

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

Ключевые преимущества горутин перед потоками ОС:

  • Быстрое создание — десятки наносекунд vs микросекунды/миллисекунды у потоков
  • Малый расход памяти — начальный стек 2 КБ vs 1-8 МБ у потоков ОС
  • Кооперативная многозадачность с вытеснением в определенных точках
  • Интеграция с каналами как примитив синхронизации первого класса
  • Эффективное планирование в пространстве пользователя без переключения в ядро ОС

7. Пример полного цикла жизни горутины

package main

import (
    "fmt"
    "time"
)

func worker(id int, ch chan string) {
    // Горгутина начинает выполнение здесь
    for i := 0; i < 3; i++ {
        ch <- fmt.Sprintf("Воркер %d: шаг %d", id, i)
        time.Sleep(100 * time.Millisecond)
    }
    close(ch)
}

func main() {
    // Создание канала для коммуникации
    ch := make(chan string, 2)
    
    // Запуск горутины
    go worker(1, ch)
    
    // Чтение результатов из горутины
    for msg := range ch {
        fmt.Println(msg)
    }
    
    // Горутина завершилась, сборщик мусора освободит ресурсы
}

8. Важные аспекты выполнения

При запуске горутины следует учитывать:

  • Горутины выполняются конкурентно, но не обязательно параллельно (зависит от GOMAXPROCS)
  • Паника в горутине завершает только эту горутину, если не восстановлена
  • Горутины не имеют идентификаторов в отличие от потоков ОС
  • Планировщик может приостанавливать горутины в точках вызовов функций, операций с каналами и сборки мусора

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