Как устроен runtime в Go?
Sobes Copilot
Получайте ответы в реальном времени
Ответ от нейросети
sobes.tech AI
Runtime Go состоит из следующих ключевых компонент: планировщик (scheduler), сборщик мусора (garbage collector) и система goroutine'ов.
-
Планировщик (ोसcheduler): Реализует многопоточность в пространстве пользователя (user-space threading). Он сопоставляет M (many) пользовательских горутин с N (few) потоками операционной системы. Использует модель M:N, которая эффективнее, чем 1:1 (каждая горутина — отдельный поток ОС) или N:1 (все горутины — один поток ОС). Планировщик управляет тремя очередями:
- Глобальная очередь (
global run queue): горутины, которые еще не assigned к P. - Локальная очередь (
local run queue): горутины, assigned к конкретной P. - Очередь ожидания (
wait queue): горутины, заблокированные по внешним причинам (сеть, файловый ввод/вывод).
Модель планировщика основана на P (processor) - логическом процессоре, который связывает G (goroutine) и M (OS thread). M выполняет код G, а P предоставляет ресурсы и контекст для выполнения (локальную очередь горутин, кеш). Планировщик распределяет горутины между доступными M и P.
- Глобальная очередь (
-
Сборщик мусора (Garbage Collector - GC): В Go используется конкурентный и параллельный сборщик мусора. Он работает concurrently с пользовательским кодом (stop-the-world фазы минимизированы) и параллельно (использует несколько ядер CPU). Алгоритм Mark-and-Sweep с триколорной схемой.
Финализация объектов также осуществляется runtime'ом.
-
Goroutine'ы: Легковесные, конкуретные функции, управляемые runtime Go, а не ОС. Они требуют меньше памяти (изначально 2KB стека, который может расти) и переключение между ними быстрее, чем между потоками ОС. Создаются с помощью ключевого слова
go.Коммуникация между горутинами происходит через каналы (channels), которые являются безопасным способом обмена данными и синхронизации.
// Пример создания горутины go func() { // Код, выполняющийся в отдельной горутине }() // Пример использования канала ch := make(chan int) go func() { ch <- 1 // Отправка данных в канал }() val := <-ch // Получение данных из канала -
Система ввода/вывода (I/O): Runtime Go использует неблокирующие I/O операции и мультиплексирование событий (например,
epollна Linux,kqueueна FreeBSD/macOS,IOCPна Windows). Когда горутина блокируется на I/O, runtime отсоединяет ее от M, позволяет M выполнять другую горутину, а когда I/O завершен, планировщик снова назначает горутине M (возможно, другую). Это позволяет эффективно использовать системные ресурсы и избегать блокировки потоков ОС. -
Стек вызовов: Горутины используют растущий стек с изменяемым размером, который начинается с небольшого размера и увеличивается динамически по мере необходимости. Это уменьшает использование памяти по сравнению с потоками ОС, у которых обычно фиксированный (и больший) размер стека.
В целом, runtime Go спроектирован для обеспечения высокой производительности и конкурентности, управляя горутинами, памятью и системными ресурсами эффективно и прозрачно для разработчика.