Go Academy · Урок

Запуск горутин

Ключевое слово go, жизненный цикл горутины и WaitGroups

Урок 1 из 412 шагов

«Запуск горутин» — бесплатный урок Go Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Go Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Go Academy содержит 4 уроков всего.

Что такое goroutine

goroutine — это лёгкая функция, выполняющаяся параллельно и управляемая средой выполнения Go. goroutine гораздо дешевле потоков ОС — можно запускать тысячи таких функций:

  • Запускаются с помощью ключевого слова go
  • Начальный стек — около 2 КБ (увеличивается динамически)
  • Планируются средой выполнения Go, а не ОС
  • Обмениваются данными через каналы, а не через общую память

Ключевое слово go

Добавьте go перед любым вызовом функции, чтобы запустить её как goroutine:

package main
import ("fmt"; "time")

func greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

func main() {
    go greet("Alice")   // runs concurrently
    go greet("Bob")     // runs concurrently
    time.Sleep(100 * time.Millisecond) // wait for goroutines
    fmt.Println("done")
}

Жизненный цикл goroutine

goroutine запускается при выполнении оператора go и завершается, когда её функция возвращает управление. Если функция main возвращает управление, все goroutine завершаются:

package main
import ("fmt"; "time")

func worker(id int) {
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Duration(id) * 50 * time.Millisecond)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    for i := 1; i <= 3; i++ {
        go worker(i)
    }
    time.Sleep(300 * time.Millisecond) // naive wait
}

sync.WaitGroup для координации

Используйте sync.WaitGroup вместо сна, чтобы дождаться завершения goroutine:

package main
import ("fmt"; "sync")

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()  // signal completion
    fmt.Printf("Worker %d working\n", id)
}

func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 5; i++ {
        wg.Add(1)           // register one goroutine
        go worker(i, &wg)
    }
    wg.Wait()               // block until all Done()
    fmt.Println("all workers finished")
}

Анонимные goroutine

Запускайте анонимную функцию непосредственно как goroutine — это удобно для небольших параллельных задач:

package main
import ("fmt"; "sync")

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        i := i  // capture loop variable
        wg.Add(1)
        go func() {
            defer wg.Done()
            fmt.Printf("goroutine %d\n", i)
        }()
    }
    wg.Wait()
}

Особенность захвата переменной цикла

Если не захватить переменную цикла, все goroutine могут увидеть одно и то же (последнее) значение — это классическая ошибка параллелизма в Go:

package main
import ("fmt"; "sync")

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(n int) {  // pass i as argument — safe
            defer wg.Done()
            fmt.Println(n)  // prints 0, 1, 2 in some order
        }(i)
    }
    wg.Wait()
}

goroutine и потоки

Почему goroutine предпочтительнее потоков ОС:

  • Начальный стек 2 КБ против примерно 1 МБ у потоков ОС
  • Создание занимает микросекунды, а не миллисекунды
  • Среда выполнения Go распределяет goroutine между потоками ОС в количестве GOMAXPROCS
  • Переключение между goroutine выполняется кооперативно — без участия ядра
  • Go берёт планирование на себя, а Вы сосредотачиваетесь на логике

GOMAXPROCS

runtime.GOMAXPROCS управляет количеством потоков ОС, которые одновременно выполняют goroutine (по умолчанию используется количество процессоров):

package main
import ("fmt"; "runtime")

func main() {
    // Default: number of available CPU cores
    fmt.Println("CPUs:", runtime.NumCPU())
    fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // 0 = query without changing

    // Set to 1 for debugging race conditions:
    // runtime.GOMAXPROCS(1)

    fmt.Println("Goroutines:", runtime.NumGoroutine())
}

Утечки goroutine

Утечка goroutine возникает, когда goroutine запущена, но никогда не завершается. Распространённые причины: заблокированное получение из канала, заблокированная блокировка или отсутствие отмены:

package main
import ("fmt"; "time"; "runtime")

func leaky() {
    ch := make(chan int)  // nobody sends to ch
    go func() {
        v := <-ch  // blocks forever — goroutine leaked!
        fmt.Println(v)
    }()
}

func main() {
    leaky()
    time.Sleep(10 * time.Millisecond)
    fmt.Println("goroutines:", runtime.NumGoroutine()) // > 1
}

Рекомендации по goroutine

Рекомендации по работе с goroutine:

  • Всегда знайте, каким образом goroutine завершится
  • Для синхронизации используйте WaitGroup или каналы, а не сон
  • Передавайте переменные цикла как аргументы функции, а не через замыкание
  • Отменяйте goroutine с помощью контекста, не оставляйте их выполняться
  • Используйте детектор состязаний: go run -race main.go

Краткая проверка

Как правильно избежать ошибки захвата переменной цикла в goroutine?

Повторение: запуск goroutine

Краткое содержание:

  • Запускайте с помощью go fn() — это легко и дёшево
  • Используйте sync.WaitGroup для ожидания: Add(1), defer Done(), Wait()
  • Захватывайте переменные цикла через аргумент или повторное объявление
  • Всегда знайте, как завершается каждая goroutine, — предотвращайте утечки
  • Используйте флаг -race для обнаружения состязаний данных
Можно начать бесплатно

Изучай Go с ИИ-репетитором — бесплатно

Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.

Курсы
51
Уроки
203

Часто задаваемые вопросы

Урок «Запуск горутин» бесплатный?

Да — полный текст урока «Запуск горутин» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Go Academy, подпишись на CoddyKit PRO. Курс Go Academy содержит 4 уроков всего.

Чему я научусь в уроке «Запуск горутин»?

Ключевое слово go, жизненный цикл горутины и WaitGroups Ты практикуешь Go Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Go Academy?

Предыдущий опыт не требуется. Go Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Запуск горутин»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Go Academy?

Да. Каждый урок Go Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Запуск горутин
  2. Небуферизованные каналы
  3. Буферизованные каналы
  4. Направление каналов и шаблоны конвейеров
← Назад к Go Academy