Как устроен runtime в языке Go?
Sobes Copilot
Получайте ответы в реальном времени
Ответ от нейросети
sobes.tech AI
Runtime в Go — это система времени выполнения, которая управляет выполнением Go-программ. Она отвечает за:
- Планирование: Использует планировщик M:N, который связывает M легковесных горутин с N системными потоками ОС. Это позволяет эффективно использовать ядра процессора.
- Управление памятью: Реализует автоматический сборщик мусора (GC), который периодически высвобождает неиспользуемую память. GC в Go — Concurrent, Tri-color Mark-and-sweep, что позволяет ему работать одновременно с пользовательским кодом и сокращать паузы.
- Управление горутинами: Создание, выполнение, переключение контекста и завершение горутин. Горутины легче потоков ОС, их создание и переключение контекста менее затратно.
- Каналы (Channels): Реализует механизм синхронизации и обмена данными между горутинами. Каналы могут быть буферизованными или небуферизованными.
- Сетевые операции: Встроенные примитивы для работы с сетью, которые интегрированы с планировщиком.
- Системные вызовы: Абстрагирует взаимодействие с операционной системой.
Основные компоненты планировщика:
- M (Machine): Поток ОС.
- P (Processor): Логический процессор, представляющий контекст, в котором выполняются горутины (G). Каждый P имеет локальную очередь готовых к выполнению горутин (
runqueue). Количество P по умолчанию равноruntime.NumCPU(). - G (Goroutine): Легковесный поток выполнения.
Когда горутина готова к выполнению, она помещается в локальную очередь P. Если локальная очередь P пуста, планировщик может "украсть" горутину из очереди другого P (work stealing). Если все P заняты и их локальные очереди пусты, планировщик может забрать горутину из глобальной очереди.
Операции ввода/вывода блокирующие для горутин, но не для потоков ОС. Когда горутина выполняет блокирующий системный вызов, она отключается от своего P, а планировщик подключает к этому P другую готовую горутину. Поток ОС (M) становится блокированным, пока системный вызов не завершится. После завершения системного вызова горутина снова становится доступной для планирования и может быть назначена на любой свободный P.
Пример создания горутины:
package main
import "fmt"
func main() {
go func() { // Создание новой горутины
fmt.Println("Привет из горутины!")
}()
fmt.Println("Привет из main!")
// В реальном приложении требуется механизм синхронизации,
// чтобы основная горутина не завершилась раньше фоновой.
}
Пример использования канала:
package main
import "fmt"
func main() {
messages := make(chan string) // Создание небуферизованного канала
go func() {
messages <- "Привет из горутины!" // Отправка сообщения в канал
}()
msg := <-messages // Прием сообщения из канала
fmt.Println(msg)
}