# Горутины

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

Горутина (goroutine) — это функция, выполняющаяся конкурентно с другими горутинами в том же адресном пространстве.

Запустить горутину очень просто:\
&#x20;**`go`**` ``normalFunc(args...)`\
\
&#x20;Функция `normalFunc(args...)` начнет выполняться асинхронно с вызвавшим ее кодом.\
\
&#x20;Обратите внимание, горутины **очень легковесны**. Практически все расходы — это создание стека, который очень невелик, хотя при необходимости может расти.<br>

Каждая горутина, как правило, представляет вызов функции, и последовательно выполняет все свои инструкции. Когда мы запускаем программу на Go, мы уже работаем как минимум с одной горутиной, которая представлена функцией main. Эта функция последовательно выполняет все инструкции, которые определены внутри нее.

Каждая программа на Go по умолчанию создает одну главную горутину — main() . Горутины выполняются поочередно, за их планирование отвечает планировщик Go. Переключение контекста между горутинами происходит в момент блокировки выполнения текущей горутины. Блокировки могут происходить при использовании примитивов для блокировок или при чтении/записи в канал. Функция time.Sleep() также блокирует выполнение функции, по этому при ее вызове происходит переключение контекста на другие горутины.

**Анонимные горутины**&#x20;

Анонимные горутины — те же анонимные функции, только вызваны используя ключевое слово go . Давайте добавим спинер ожидания, чтобы разбавить нашу програму.

#### Планировщик горутин

[Планировщик горутин (Goroutine scheduler)](https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/runtime/proc.go#L19) является перехватывающим задачи (work-stealing) планировщиком, который был введен еще в Go 1.1 Дмитрием Вьюковым вместе с командой Go. Его диздок доступен [здесь](https://golang.org/s/go11sched) и включает рассуждения на тему возможных будущих улучшений. Существует множество [замечательных](https://www.ardanlabs.com/blog/2018/08/scheduling-in-go-part1.html) [ресурсов](https://rakyll.org/scheduler/), помогающих разобраться, как он работает, но основная суть заключается в том, что он пытается управлять **G**, **M** и **P**; горутинами, машинами (потоками) и процессорами.

«G» - это просто горутина Golang.

«M» - это поток ОС, который может выполнять что-либо или же бездействовать.

«P» можно рассматривать как ЦП в планировщике ОС; он представляет ресурсы, необходимые для выполнения нашего Go кода, такие как планировщик или состояние распределителя памяти.

В рантайме они представлены как структуры: [type g](https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/runtime/runtime2.go#L395), [type m](https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/runtime/runtime2.go#L473) или [type p](https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/runtime/runtime2.go#L552).

***Основная задача планировщика состоит в том, чтобы сопоставить каждую G (код, который мы хотим выполнить) с M (где его выполнять) и P (права и ресурсы для выполнения).***

Когда M прекращает выполнение нашего кода, он возвращает свой P в пул свободных P. Чтобы возобновить выполнение Go кода, он должен повторно заполучить его. Точно так же, когда горутина завершается, объект G возвращается в пул свободных G и позже может быть повторно использован для какой-либо другой горутины.

#### Стек горутины

Первое поле структуры `g` имеет тип `stack`.

```
type g struct {
	// Параметры стека.
	// stack описывает фактическую память стека: [stack.lo, stack.hi).
	// stackguard0 - указатель стека, сравниваемый по мере роста стека Go.
	// stackguard1 - указатель стека, сравниваемый по мере роста стека C.
...
	stack       stack  // смещение, известное runtime/cgo
	stackguard0 uintptr // смещение, известное liblink
	stackguard1 uintptr // смещение, известное liblink
```

Сам стек представляет собой не что иное, как два значения, обозначающих его начало и конец.

```
type stack struct {
	lo uintptr
	hi uintptr
}
```

К этому времени вы, вероятно, зададитесь вопросом: *«А каков же размер этого стека?»*, или уже догадаетесь, что 2 килобайта относятся к этому стеку!

***Горутина*** [***стартует***](https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/runtime/proc.go#L3410) ***с минимального размера стека в*** [***2 килобайта***](https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/runtime/stack.go#L72)***, который увеличивается и уменьшается по мере необходимости без риска когда-либо закончиться.***

[Эта](https://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite) отличная статья Дэйва Чейни более подробно объясняет, как это работает. По сути, перед выполнением любой функции Go проверяет, доступен ли объем стека, необходимый для функции, которую он собирается выполнить; если нет, то выполняется вызов [runtime.morestack,](https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/runtime/asm_amd64.s#L407) который выделяет новую страницу, и только после этого выполняется функция. Наконец, когда эта функция завершается, ее возвращаемые аргументы копируются обратно в исходный фрейм стека, а все невостребованное пространство стека высвобождается.

Хотя минимальный размер стека определен как 2048 байтов, рантайм Go также не позволяет горутинам превышать [максимальный размер стека](https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/runtime/stack.go#L1031); этот максимум зависит от архитектуры и составляет [1 ГБ для 64-разрядных систем и 250 МБ для 32-разрядных](https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/runtime/proc.go#L120) систем.

Если этот предел достигнут, будет выполнен вызов [runtime.abort](https://github.com/golang/go/blob/f296b7a6f045325a230f77e9bda1470b1270f817/src/runtime/asm_amd64.s#L450).

## Горутины <a href="#undefined" id="undefined"></a>

1. Кооперативная многозадачность
2. Модель N:M
3. Компилятор знает про используемые регистры
4. 2kb памяти на стек для горутины (linux)
5. Выделение памяти при запуске

## Переключение между горутинами <a href="#undefined" id="undefined"></a>

1. Каналы
2. Блокирующие системные вызовы
3. Сборка мусора
4. Вызов функций (сплит стека)
5. Нетполлер (net pool)
6. Добровольно (runtime.gosched)

![](https://1596906813-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MSIObV-s_nRzdVoJ5_8%2F-MWsxA_k8vk8A1C08mrP%2F-MWszgtbxQ-i3DQoqNha%2F%D0%B8%D0%B7%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5.png?alt=media\&token=ea1c65a1-e02e-41d7-9493-ceb7e078f139)
