Как этот выбор работает в goroutine?

#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 инструкции выполняется попытка выбрать первое из двух следующих для завершения:

  1. c <- x
  2. <- 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, похоже, учится.