Что запускает горутину?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Механизм запуска горутины в Go
Горутину запускает не какой-то один конкретный механизм, а ключевое слово go, которое является частью языка Go. Когда компилятор встречает go перед вызовом функции, он создаёт новую горутину — легковесный поток выполнения, управляемый рантаймом Go (runtime), а не операционной системой напрямую.
Основные способы запуска
1. Прямой запуск с помощью go
Самый распространённый способ — использование ключевого слова перед вызовом функции:
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Привет из горутины!")
}
func main() {
// Запускаем горутину
go sayHello()
// Даём время на выполнение горутины
time.Sleep(100 * time.Millisecond)
fmt.Println("Главная горутина завершает работу")
}
2. Запуск анонимной функции
Часто используется для инлайнового выполнения:
go func() {
fmt.Println("Анонимная горутина выполняется")
// Выполнение какой-либо работы
}()
3. Запуск методов
Горутины можно запускать и для методов структур:
type Worker struct {
name string
}
func (w Worker) Process() {
fmt.Printf("Воркер %s обрабатывает данные\n", w.name)
}
func main() {
worker := Worker{name: "Гор"}
go worker.Process()
time.Sleep(50 * time.Millisecond)
}
Что происходит "под капотом"?
Когда вы используете go func(), происходит следующее:
-
Выделение стека — рантайм Go выделяет стек для новой горутины (начальный размер обычно 2-8 КБ, динамически растёт/уменьшается).
-
Создание структуры горутины — в рантайме создаётся объект
g, содержащий метаданные: состояние выполнения, стек, информацию о планировании. -
Постановка в очередь планировщика — горутина помещается в локальную очередь планировщика (
P), который управляет выполнением горутин на потоках ОС (M). -
Запуск планировщиком — когда наступает очередь выполнения, планировщик Go (scheduler) назначает горутину на доступный поток ОС.
Ключевые особенности запуска
-
Независимость от главной горутины — запущенная горутина выполняется параллельно с вызвавшим её кодом (если есть доступные ядра CPU).
-
Немедленный возврат управления — оператор
goне блокирует выполнение текущей горутины. Управление возвращается сразу после постановки в очередь планировщика. -
Порядок запуска ≠ порядок выполнения — нет гарантий, когда именно начнёт выполняться горутина:
for i := 0; i < 5; i++ {
go func(n int) {
fmt.Printf("Горутина %d\n", n)
}(i)
}
// Порядок вывода может быть любым!
- Зависимость от главной горутины — если главная горутина (функция
main) завершается, все остальные горутины принудительно останавливаются, даже если не закончили работу.
Практические аспекты запуска
Передача параметров
Важно правильно передавать параметры в запускаемую горутину:
// ПРАВИЛЬНО — параметр передаётся как аргумент
for i := 0; i < 3; i++ {
go func(id int) {
fmt.Printf("Горутина %d\n", id)
}(i)
}
// НЕПРАВИЛЬНО — ловушка замыкания
for i := 0; i < 3; i++ {
go func() {
fmt.Printf("Горутина %d\n", i) // Все увидят значение 3!
}()
}
Использование WaitGroup для синхронизации
Для ожидания завершения горутин используют sync.WaitGroup:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1) // Увеличиваем счётчик
go func(id int) {
defer wg.Done() // Уменьшаем при завершении
fmt.Printf("Горутина %d завершена\n", id)
}(i)
}
wg.Wait() // Ждём завершения всех горутин
fmt.Println("Все горутины завершены")
}
Что НЕ запускает горутину?
Важно понимать, что следующие конструкции НЕ создают новые горутины:
- Обычный вызов функции
function()— выполняется в текущей горутине - Вызов метода
object.Method()— выполняется в текущей горутине - Замыкания (closures) — сами по себе не создают горутины
Заключение
Горутину запускает ключевое слово go, которое инициирует цепочку действий в рантайме Go: выделение ресурсов, создание структуры управления и постановку в очередь планировщика. Этот механизм является фундаментальным для конкурентного программирования на Go и позволяет эффективно использовать ресурсы процессора для параллельного выполнения тысяч и даже миллионов легковесных потоков выполнения. Понимание этого процесса критически важно для написания корректных, эффективных и безопасных конкурентных программ на Go.