Горутины — это функции или методы, выполняемые конкурентно с другими горутинами в одном и том же адресном пространстве. Они легковеснее традиционных потоков, занимают меньше памяти и позволяют эффективно использовать ядра процессора.
Запуск горутин
Запуск горутины происходит с помощью ключевого слова go
, за которым следует вызов функции:
func sayHello() {
fmt.Println("Hello, world!")
}
func main() {
go sayHello()
time.Sleep(1 * time.Second) // Даем время горутине на выполнение
}
Давайте теперь запустим несколько горутин:
func sayHello(i int) {
fmt.Println("Hello, world! I'm", i, "goroutine!")
}
func main() {
for i := range 5 {
go sayHello(i)
}
time.Sleep(1 * time.Second) // Даем время горутинам на выполнение
}
Таким образом получим вывод:
Hello, world! I'm 4 goroutine!
Hello, world! I'm 0 goroutine!
Hello, world! I'm 1 goroutine!
Hello, world! I'm 2 goroutine!
Hello, world! I'm 3 goroutine!
Обратите внимание, что порядок выполнения горутин не гарантирован.
Ожидание горутин
В примерах выше мы ожидали выполнение горутин с помощью time.Sleep(1 * time.Second)
в самом конце main()
А зачем вообще ждать пока горутины выполнятся?
Ожидание завершения горутин — критически важная часть управления конкурентными операциями в Go. Без явного ожидания горутин главный поток может завершиться раньше, чем горутины успеют выполнить свою работу, что приведет к тому, что горутины просто не выполнятся.
Использование WaitGroup
Пожалуй самый простой способ дождаться выполнения всех горутин это прибегнуть к sync.WaitGroup
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait() // Ожидаем завершения всех горутин
}
Суть довольно проста:
wg.Add(1)
добавляет единицу к счетчикуwg.Done()
вычитает единицу из счетчикаwg.Wait()
ожидает когда счетчик будет равен нулю
Таким образом wg.Wait()
дождется пока каждая из горутин выполнит wg.Done()
Коммуникация горутин
Для коммуникации между горутинами чаще всего используют каналы.
func sayHello(i int, message chan string) {
message <- fmt.Sprint("Hello, world! I'm ", i, " goroutine!") // Отправляем сообщение в канал
}
func main() {
message := make(chan string) // Создаем канал для строк
for i := range 5 {
go sayHello(i, message)
}
for range 5 {
fmt.Println(<-message) // Читаем сообщения из канала
}
close(message)
}
В данном примере мы создали канал типа string
, породили 5 горутин, отправили в канал 5 сообщений и вывели все сообщения в терминал.
В примере нет необходимости использовать WaitGroup
так как мы ожидаем выполнения всех горутин при помощи механизма чтения из канала.
Заключение
Эта статья представляет собой лишь верхушку айсберга возможностей горутин и каналов в Go, и служит отправной точкой для более углубленного изучения.