Горутины
Чему вы научитесь:
- объяснять, чем конкурентность отличается от параллелизма и горутина — от потока;
- запускать горутины и дожидаться их через
sync.WaitGroup; - передавать значения между горутинами по каналам, закрывать каналы и ждать нескольких каналов через
select; - защищать общие данные от гонки данных с помощью
sync.Mutex.

Представьте кухню ресторана. Один повар может готовить три блюда сразу: ставит суп, пока он варится — режет салат, потом возвращается к супу. В каждый момент он делает что-то одно, но все три блюда продвигаются. Три повара у трёх плит работают иначе: каждый занят своим блюдом в одно и то же мгновение.
Конкурентность и параллелизм
Эти две картинки — два разных понятия.
Конкурентность
Конкурентность — когда программа состоит из нескольких независимых задач, которые продвигаются вперемешку. Это один повар и три блюда.
Конкурентность — про то, как устроена программа: из каких задач она состоит.
Параллелизм
Параллелизм — когда несколько задач выполняются буквально одновременно, на разных ядрах процессора. Это три повара у трёх плит.
Ядро — часть процессора, которая выполняет одну последовательность команд за раз. Параллелизм — про то, как программа выполняется.
Чем они отличаются
Этот вопрос часто задают на собеседовании: «Чем конкурентность отличается от параллелизма?» Ответ собирается из трёх пунктов:
- конкурентность — про то, как устроена программа: из каких задач она состоит;
- параллелизм — про то, как она выполняется;
- конкурентная программа на нескольких ядрах может работать параллельно, а на одном ядре её задачи идут по очереди.
Роб Пайк, один из авторов Go, сформулировал так: конкурентность — это работа со многими делами сразу, а параллелизм — выполнение многих дел сразу.
Горутины и потоки
В Go каждый повар на этой кухне — горутина: функция, которая выполняется одновременно с остальной программой. Функция main тоже работает в горутине — главной.
Поток
Операционная система выполняет программы в потоках. Поток стоит дорого:
- обычно под него сразу резервируют около мегабайта памяти или больше;
- создаёт и переключает потоки сама операционная система.
Горутина
Горутине на старте хватает нескольких килобайт, и память она добирает, только когда ей нужно. Поэтому программа на Go может держать тысячи и даже сотни тысяч горутин.
Среда выполнения Go
Распределяет горутины среда выполнения Go — код, который Go добавляет в каждую программу. Она работает так:
- раскладывает много горутин на немного потоков;
- по умолчанию одновременно код Go выполняют столько потоков, сколько ядер доступно программе;
- остальные горутины в это время ждут своей очереди, как повара ждут свободную плиту.
Здесь аналогия с кухней заканчивается. Настоящий шеф заранее знает, кто за кем встанет к плите. А среда выполнения Go ничего не обещает: какая горутина когда работает, может меняться от запуска к запуску.
Оператор go
Программа на Go выполняет вызовы по очереди: следующая строка ждёт, пока функция не вернётся. Ключевое слово go перед вызовом меняет это правило. Вызов запускается в новой горутине, а main идёт дальше, не дожидаясь его:
package main
import "fmt"
func say(word string) {
fmt.Println(word)
}
func main() {
go say("hello")
fmt.Println("main")
}Давайте разберём, что здесь происходит:
go say("hello")нанимает нового повара и сразу возвращает управление.- Go вычисляет аргументы сразу, на строке с
go, а телоsayвыполняет в новой горутине. - Go отбрасывает результаты функции.
fmt.Println("main")печатаетmain, иmainвозвращается.
Строку main программа печатает при каждом запуске, а hello — не всегда. Вместе с main заканчивается вся программа. Если say не успела дойти до fmt.Println, её вывод пропадает: кухня закрылась вместе с поварами.
Горутина из анонимной функции
Оператор go работает с любым вызовом:
- функции;
- метода;
- анонимной функции.
Часто в горутине запускают анонимную функцию. Круглые скобки после её тела — это вызов:
for i := range 3 {
go func() {
fmt.Println(i)
}()
}Разберём, что здесь происходит:
- Каждая итерация запускает свою горутину, и каждая видит своё значение
i. mainи здесь их не ждёт.- Цикл может напечатать
0,1и2в любом порядке, только часть чисел или ничего.
Полагаться на порядок работы горутин нельзя.
Горутины в браузере
Задачи курса выполняются в браузере, где у программы одно ядро. Конкурентность там работает, а параллелизма нет: горутины выполняются по очереди. Но порядок их работы и там не задан.
Частая ошибка
Анонимную функцию передали в go, но не вызвали — забыли скобки после тела:
go func() {
fmt.Println("hello")
} // ✗expression in go must be function callПрограмма не компилируется: оператору go нужен вызов функции, а не сама функция. Исправление — поставить () после закрывающей скобки: }().
Главное
- Конкурентность — как программа разбита на задачи; параллелизм — их одновременное выполнение на разных ядрах.
- Горутина намного дешевле потока: среда выполнения Go раскладывает много горутин на немного потоков.
go f()запускаетfв новой горутине; когдаmainвозвращается, программа заканчивается, не дожидаясь остальных.- Порядок работы горутин не задан и может меняться от запуска к запуску.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.