#go #goroutine
#Вперед #программа goroutine
Вопрос:
я следил за примерами go tour, и я не понимаю, как это работает https://tour.golang.org/concurrency/5
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
как это работает?
и пока я пытаюсь понять.
package main
import "fmt"
func b(c,quit chan int) {
c <-1
c <-2
c <-3
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i {
fmt.Println(<-c)
}
quit <- 0
}()
b(c,quit)
}
иногда это будет печатать 1,2, иногда печатать 1,2,3, почему?
Ответ №1:
Во-первых, в func fibonacci
select
инструкции выполняется попытка выбрать первое из двух следующих для завершения:
c <- x
<- quit
Это довольно легко понять <- quit
, который пытается получить значение из вызываемого канала quit
(и игнорирует полученное значение).
c <- x
означает отправку значения, равного (является копией) x. Похоже на разблокировку, но в Go отправка по небуферизованному каналу (что объясняется в Go tour) блокируется, когда нет получателя.
Итак, здесь это означает, ожидание готовности получателя к приему значения (или пробела в буфере, если бы это был буферизованный канал), что в этом коде означает fmt.Println(<-c)
, а затем отправка значения получателю.
Итак, этот оператор разблокируется (завершается) всякий <-c
раз, когда вычисляется. То есть каждая итерация цикла.
И для вашего кода, в то время как все значения 1
, 2
, 3
, гарантированно отправляются по каналу (и принимаются), func b
возвращаются и, следовательно func main
, сохраняются без гарантии fmt.Println(3)
завершения.
В Go, когда func main
возвращается, программа завершается, и незавершенная программа goroutine не получает возможности завершить свою работу — поэтому иногда она печатает 3
, а иногда нет.
Ответ №2:
Чтобы лучше понять пример Фибоначчи, давайте проанализируем различные части.
Сначала анонимная функция
go func() {
for i := 0; i < 10; i {
fmt.Println(<-c)
}
quit <- 0
}()
Ключевое слово «go» запустит новую goroutine, так что теперь у нас есть «основная» goroutine и эта, они будут выполняться одновременно.
Цикл сообщает нам, что мы собираемся выполнить это 10 раз:
fmt.Println(<-c)
Этот вызов будет блокироваться до тех пор, пока мы не получим целое число из канала и не напечатаем его. Как только это произойдет 10 раз, мы будем сигнализировать, что эта программа завершена
quit <- 0
Теперь давайте вернемся к основной программе goroutine, в то время как другая программа запускалась, она вызывала функцию «finbacci», ключевого слова «go» нет, поэтому этот вызов здесь блокируется.
fibonacci(c, quit)
Теперь давайте проанализируем функцию
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x y
case <-quit:
fmt.Println("quit")
return
}
}
Мы запускаем бесконечный цикл и на каждой итерации мы будем пытаться выполнить один случай из select
В первом случае будет предпринята попытка бесконечно отправлять значения последовательности Фибоначчи в канал, в какой-то момент, как только другая программа достигнет fmt.Println(<-c)
инструкции, этот случай будет выполнен, значения будут пересчитаны, и произойдет следующая итерация цикла.
case c <- x:
x, y = y, x y
Второй случай пока не сможет быть запущен, поскольку никто ничего не отправляет на канал «quit».
case <-quit:
fmt.Println("quit")
return
}
После 10 итераций первая goroutine перестанет получать новые значения, поэтому первый случай выбора не сможет выполняться
case c <- x: // this will block after 10 times, nobody is reading
x, y = y, x y
На данный момент ни один регистр в «select» не может быть выполнен, основная программа goroutine «заблокирована» (скорее приостановлена с логической точки зрения).
Наконец, в какой-то момент первая goroutine сообщит, что она завершила использование канала «quit»
quit <- 0
Это позволяет выполнить второй случай «select», который разорвет бесконечный цикл и позволит вернуть функцию «фибоначчи», а основная функция сможет завершиться чистым способом.
Надеюсь, это поможет вам понять пример Фибоначчи.
Теперь, переходя к вашему коду, вы не ждете завершения анонимной goroutine, фактически, она даже не завершается. Ваш метод «b» отправляет «1,2,3» и немедленно возвращается.
Поскольку это последняя часть основной функции, программа завершается.
Если вы иногда видите только «1,2», это потому, что «fmt.Оператор Println» выполняется слишком медленно, и программа завершается до того, как сможет распечатать 3.
Комментарии:
1. Еще одна вещь, которую нужно добавить, все будет немного (или совсем) иначе, если вместо небуферизованных каналов будут использоваться буферизованные (используемые в настоящее время). Но это очень хорошо объяснено.
2. @shmsr спасибо за обзор и предлагаемые исправления! Вы правы насчет небуферизованных каналов, я просто не хотел вводить больше концепций, OP, похоже, учится.