Сколько памяти занимает горутина?

Go

введение

Я считаю, что студенты, знакомые с языком Go, должны были слышать о сопрограммах Go, то есть о концепции горутин.Что касается введения в горутины, в большинстве статей упоминается, что горутины очень легковесны по сравнению с потоками.Размер памяти , может запускать больше горутин. Но несколько статей объясняют, как горутины занимают меньше ресурсов Сколько памяти занимает одна горутина? Эта статья объяснит эти вопросы.

Некоторые основные выводы

  • Память, занятая горутинами, управляется в стеке.
  • Размер стека, занимаемого горутиной, определяетсяruntimeРаспределить по требованию
  • Возьмем, к примеру, 64-битную JVM, которая по умолчанию выделяет 1 МБ пространства стека для каждого потока.Если размер не будет правильно выделен, возникнет проблема переполнения стека.

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

стек сегментов

В более ранних версиях Go использование подстекового подхода к управлению памятью при создании горутины,runtimeДля сопрограммы выделена область памяти размером 8 КБ. Итак, вопрос, что делать, если места в 8 КБ недостаточно?

Чтобы решить эту проблему, Go будет вставлять небольшой фрагмент предварительного кода при входе в каждую функцию, он может проверять, исчерпано ли место в стеке, и если оно израсходовано, он будет вызывать функцию morestack() для расширения космос.

Механизм функции morestack(), то есть механизм расширения стека сегментов: выделение новой области памяти для пространства стека. Структура в нижней части этого нового стека затем заполняется различными данными о стеке, включая адрес старого стека. Когда получен новый сегмент стека, горутина перезапускается путем повторного выполнения функции, которая вызывает использование стека. Это называется разделением стека

  +---------------+
  |               |
  |   unused      |
  |   stack       |
  |   space       |
  +---------------+
  |    test       |
  |               |
  +---------------+
  |               |
  |  lessstack    |
  +---------------+
  | Stack info    |
  |               |-----+
  +---------------+     |
                        |
                        |
  +---------------+     |
  |    test       |     |
  |               | <---+
  +---------------+
  | rest of stack |
  |               |

Механизм возврата стека сегментов: как показано на рисунке выше, новый стек будет вставлять запись стека для lessstack(). Эта функция на самом деле не вызывается явно. Он будет установлен, когда функция, исчерпавшая старый стек, вернется, например, test() на рисунке. стек и настроить указатель стека (SP), чтобы иметь возможность вернуться к предыдущему сегменту стека. Затем можно освободить пространство нового сегмента стека.

Проблемы с сегментированными стеками

Механизм сегментированного стека позволяет стеку расширяться и сжиматься по мере необходимости. Программисту не нужно заботиться о размере стека.

Но у сегментированных стеков есть и недостатки. Сжатие стека — относительно дорогая операция. Ситуация более очевидна, если стек разбивается в цикле. Функция увеличивает стек, разбивает стек, возвращает стек и освобождает сегменты стека. Если вы выполняете эти операции в цикле, вы будете платить много накладных расходов. Например, если цикл проходит через эти процессы один раз, стек снова будет исчерпан в следующем цикле, и сегмент стека должен быть перераспределен, а затем снова освобожден.

Это известно как проблема горячего разделения. Это основная причина, по которой команда разработчиков Golang перешла на новый метод управления стеком, называемый копированием стека.

непрерывный штабель

Начиная с GO1.4 официально используется механизм непрерывного стека.

Копии стека начинаются так же, как сегментированные стеки. Сопрограмма запускается, использует пространство стека и запускает то же самое обнаружение переполнения стека, когда стек вот-вот закончится.

Однако вместо обратной ссылки в стеке сегментов копирование стека создает новый сегмент, вдвое превышающий размер старого стека, и полностью копирует старый стек. Таким образом, среда выполнения ничего не делает, когда стек сжимается до прежнего размера. Сжатие становится бесшумной свободной операцией. Кроме того, когда стек снова растет, среде выполнения не нужно ничего делать, просто повторно использовать только что расширенное пространство.

Это не так просто, как кажется, но копирование стека — сложная задача. Поскольку переменные в стеке могут получить свои адреса в Golang, вы получите указатель на стек. И если вы легко скопируете и переместите стек, любые указатели на старый стек станут недействительными.

И механизм безопасности памяти Golang предусматривает, что любой указатель, который может указывать на стек, должен существовать в стеке.

Таким образом, сборщик мусора может помочь копированию стека.Поскольку сборщику мусора необходимо знать, какие указатели могут быть переработаны, он может выяснить, какие части стека являются указателями.Когда выполняется копирование стека, информация об указателе будет обновлена. чтобы указать на новую цель и все соответствующие указатели.

Однако большое количество основных функций планирования и ядер GC во время выполнения написано на языке C, и эти функции не могут получить информацию об указателях, поэтому их нельзя скопировать. Это выполняется в специальном стеке, а размер стека определяется разработчиком среды выполнения.