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

Всегда ли создаётся структура под созданную горутину?

2.0 Middle🔥 221 комментариев
#Конкурентность и горутины#Основы Go

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

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

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

Отличный вопрос, который касается внутренней механики планировщика Go (scheduler). Короткий ответ: да, всегда, но это не "структура" в смысле пользовательского struct, а внутренний объект планировщика, и он может быть переиспользован.

Давайте разберем подробно, что происходит при вызове go func(){}().

Что такое G, M и P?

Чтобы понять процесс, нужно знать три ключевые абстракции рантайма Go:

  • G (Goroutine): Это и есть сама горутина. В рантайме она представлена структурой g, которая содержит всю информацию о состоянии горутины: программный счетчик (pc), указатель стека, каналы, на которые она ждет, и т.д.
  • M (Machine): Представляет поток операционной системы (OS thread). Именно M выполняет машинный код. M привязывается к ядру CPU.
  • P (Processor): Виртуальный "процессор" или контекст планировщика. P — это ресурс, который требуется M для исполнения кода горутины G. P хранит локальную очередь исполняемых горутин. Количество P по умолчанию равно количеству логических ядер CPU (GOMAXPROCS).

Жизненный цикл: от go до выполнения

  1. Создание дескриптора (структуры g): Когда вы пишете go myFunction(), рантайм Go всегда создает или берет из пула новую структуру g, которая будет представлять эту горутину. В этот дескриптор записывается начальная информация: функция для выполнения, аргументы, статус (например, _Gdead).

    // Псевдокод, иллюстрирующий процесс
    func newgoroutine(fn func()) {
        // Рантайм получает или создает новый объект 'g'
        newg := runtime.malg(stackSize) // malg – allocate new g
        // Настраивает его
        newg.fn = fn
        newg.gopc = caller_pc
        newg.status = _Gdead // Изначально "мертва"
        // ...
        // Переводит в очередь на выполнение
        runqput(_g_.m.p.ptr(), newg) // Помещает в локальную очередь P
    }
    
  2. Помещение в очередь: Свежесозданная горутина G помещается в локальную очередь исполнения (runqueue) того P, к которому привязан текущий поток M, выполняющий ваш код. Это происходит очень быстро, практически без блокировок.

  3. Планирование и выполнение: В какой-то момент планировщик Go на свободном потоке M (или текущем M, если он освободился) возьмет эту горутину G из очереди своего P, изменит ее статус (например, на _Grunning) и начнет выполнение ее функции.

Важные нюансы и оптимизации

Утверждение "всегда создается структура" абсолютно верно с точки зрения логики. Однако для производительности рантайм применяет оптимизации:

  • Пулы повторного использования: Структуры g не уничтожаются сразу после завершения работы горутины. Они помещаются в пул свободных горутин (gFree). При создании новой горутины рантайм сначала проверяет этот пул и может переиспользовать существующую, но уже неактивную, структуру g, инициализировав ее новыми значениями. Это делается для снижения нагрузки на сборщик мусора (GC) и аллокатора.
  • Статусы горутины: Структура g существует на протяжении всего жизненного цикла горутины, но ее статус меняется: _Gdead (создана/в пуле), _Grunnable (в очереди), _Grunning (исполняется), _Gwaiting (ждет канал, мьютекс, системный вызов), _Gcopystack (стек меняет размер) и, наконец, снова _Gdead по завершении.
  • Системные вызовы: Если горутина выполняет блокирующий системный вызов (например, файловый ввод-вывод), планировщик может отсоединить текущий поток M от своего P, чтобы этот P мог обслуживать другие горутины на другом потоке M. При этом структура g для ожидающей горутины остается, ее статус становится _Gsyscall. Когда системный вызов завершится, M попытается вновь захватить P и продолжить выполнение этой G.

Итог

  • Да, всегда. Каждая новая горутина немедленно получает свой собственный внутренний объект-дескриптор — структуру g. Без этого объекта планировщик не мог бы хранить ее состояние, стек, точку останова и управлять ею.
  • Это не пользовательская структура, а внутренняя структура рантайма (runtime.g).
  • Физическая память под этот дескриптор может быть взята из пула ранее завершенных горутин для оптимизации.
  • Существование этого объекта не зависит от того, выполняется ли горутина в данный момент, ждет ли она, или находится в очереди. Он существует от вызова go до момента, когда сборщик мусора окончательно освободит память (если она не была возвращена в пул).

Таким образом, горутины — это не "бесплатные" потоки в смысле памяти. Они гораздо легче потоков ОС (имеют маленький начальный стек, например, 2 КБ, и быстрый контекст-свитч), но каждая из них обладает своим собственным управляющим блоком данных в рантайме.

Всегда ли создаётся структура под созданную горутину? | PrepBro