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

Как горутины аллоцируют себе память?

2.0 Middle🔥 221 комментариев
#Конкурентность и горутины#Производительность и оптимизация

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

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

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

Аллокация памяти для горутин в Go

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

Начальная аллокация стека

При создании горутины ей выделяется начальный стек фиксированного размера: . В современных версиях Go (начиная с 1.4) это 2 КБ для 64-битных систем и 4 КБ для 32-битных систем. Это значительно меньше типичного размера стека потока ОС (обычно 1-8 МБ).

// Пример создания горутины с минимальными накладными расходами
go func() {
    // Изначально этой функции доступно 2 КБ стека
    localVar := 42
    // ... операции с переменными
}()

Динамический рост стека

Когда горутине требуется больше памяти стека, чем доступно, происходит непрерывный рост стека (continuous stack growth):

Механизм роста:

  1. Создается новый стек большего размера (обычно в 2 раза)
  2. Содержимое старого стека копируется в новый
  3. Указатели на стек обновляются
  4. Старый стек освобождается
func recursiveFunction(depth int) {
    var buffer [256]byte // Занимает место в стеке
    if depth >的系统 1000 {
        return
    }
    // При глубокой рекурсии стек будет расти автоматически
    recursiveFunction(depth + 1)
}

Архитектура разделенных стеков (до Go 1.4)

В более ранних версиях Go использовалась модель сегментированных стеков (segmented stacks):

  • При переполнении стека выделялся новый сегмент
  • При возврате к меньшему использованию стек мог "сокращаться"
  • Это приводило к проблеме "hot split" - частому выделению/освобождению сегментов

Управление памятью кучи (heap)

Важно понимать, что стек горутины используется только для локальных переменных, которые не "убегают" из функции (don't escape):

func createLocal() int {
    x := 42 // Размещается в стеке (если не убегает)
    return x
}

func createEscaping() *int {
    x := 42 // Убегает из функции - размещается в куче
    return &x
}

Решение о размещении принимает escape analysis - статический анализ на этапе компиляции, который определяет, где размещать переменные: -H Стек: если время жизни переменной ограничено функцией -H Куча: если указатель на переменную передается наружу

Память для структур данных горутины

Помимо стека, каждая горутина требует памяти для:

  • Дескриптор горутины (g struct): ~2-4 КБ
  • Информация о планировании

Контекст выполнения

Сведения о каналах и блокировках

Оптимизации и особенности

Пулы горутин:

  • Планировщик Go использует пулы для переиспользования структур горутин
  • Создание новой горутины часто означает получение готовой структуры из пула

Малые стеки:

  • Начальный размер 2 КБ оптимизирован под большинство функций
  • Позволяет создавать миллионы горутин без чрезмерного потребления памяти

Сборка мусора: -f Стеки горутин очищаются индивидуально при завершении горутины -f Память кучи, используемая горутинами, управляется общим сборщиком мусора

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

package main

import (
    "fmt"
    "runtime"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    var m1, m2 runtime.MemStats
    
    runtime.ReadMemStats(&m1)
    
    for i := 0; i < 10000; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            local := make([]byte, 128) // Локальная переменная
            _ = local
        }(i)
    }
    
    wg.Wait()
    runtime.ReadMemStats(&m2)
    
    fmt.Printf("Память под горутины: ~%v KB\n", 
        (m2.HeapAlloc-m1.HeapAlloc)/1024)
}

Ключевые преимущества подхода Go

Эффективность:

  • Низкие накладные расходы на создание (2 КБ + структура управления)
  • Динамический рост避免了预留 избыточной памяти -y Позволяет создавать десятки тысяч горутин

Безопасность:

  • Каждая горутина имеет изолированный стек
  • Автоматическое управление предотвращает переполнение

Производительность:

  • Быстрое создание и уничтожение – Минимизация обращений к аллокатору кучи благодаря escape analysis

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