Конкурентность: горутины, каналы и контекст

Горутины и sync.WaitGroup: конкурентный запуск и ожидание

Содержание курса

В прошлом уроке мы читали и писали JSON, работая с одним файлом в одном потоке выполнения. Теперь посмотрим, что происходит, когда несколько функций начинают работать одновременно — и что нужно сделать, чтобы программа не сломалась.

Горутина: конкурентный запуск и почему main не ждёт

Запустить функцию как горутину — это одна строка:

go greet("Alice")

Планировщик Go начнёт выполнять greet конкурентно с тем кодом, который идёт после этой строки. Вызывающий код не блокируется и продолжает работать немедленно.

Чтобы почувствовать разницу, посмотрим на простой пример:

package main

import "fmt"

func greet(name string) {
    fmt.Println("Hello,", name)
}

func main() {
    go greet("Alice")
    go greet("Bob")
    fmt.Println("main done")
}

Запустите это — скорее всего, вы увидите только main done, а оба Hello не появятся вовсе. Иногда одно из них мелькнет, иногда нет. Причина: как только main дошла до конца, рантайм Go завершает процесс, и все незавершённые горутины прерываются — независимо от того, что они делали.

Это ключевое свойство: горутины не привязаны к вызывающей функции по времени жизни. go greet("Alice") — это не «вызов с ожиданием результата», это «запрос к планировщику: начни это при первой возможности».

Теперь о терминах, которые часто путают. Конкурентность означает, что горутины могут перекрываться по времени — пока одна ждёт, другая продвигается вперёд.

Параллельность — это буквальное одновременное выполнение на разных ядрах CPU. Go может быть параллельным, если машина многоядерная и рантайм решит задействовать несколько потоков ОС, но гарантии параллельности нет. Ваш код должен быть корректен в обоих случаях.