这个 select 在 goroutine 中是如何工作的?

How this select works in 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,为什么?

为了更好地理解斐波那契示例,让我们分析不同的部分。

首先是匿名函数

go func() {
    for i := 0; i < 10; i++ {
        fmt.Println(<-c)
    }
    quit <- 0
}()

“go”关键字将启动一个新的 goroutine,所以现在我们有了“main”goroutine 和这个,它们将同时 运行ning。

循环告诉我们要执行 10 次:

fmt.Println(<-c)

此调用将阻塞,直到我们从通道接收到一个整数并将其打印出来。一旦这种情况发生 10 次,我们就会发出这个 goroutine 已经完成的信号

quit <- 0

现在让我们回到主 goroutine,当另一个 goroutine 启动时它调用了函数“finbonacci”,没有“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

中执行一个案例

第一种情况会尝试将斐波那契数列值无限期地发送到通道,在某个时刻,一旦另一个 goroutine 到达 fmt.Println(<-c) 语句,这种情况将被执行,值将被重新计算并且循环的下一次迭代将发生。

case c <- x:
        x, y = y, x+y 

第二种情况还不能运行,因为没有人向“退出”频道发送任何东西。

case <-quit: 
        fmt.Println("quit")
        return
}

经过 10 次迭代后,第一个 goroutine 将停止接收新值,因此第一个 select 案例将无法 运行

case c <- x: // this will block after 10 times, nobody is reading
        x, y = y, x+y 

此时,“select”中的任何case都无法执行,主goroutine被“阻塞”了(从逻辑的角度更像是暂停了)。

最后,在某个时候,第一个 goroutine 将发出信号,表明它已完成使用“退出”通道

quit <- 0

这允许执行第二个“select”案例,这将打破无限循环并允许“fibonacci”函数return并且主函数将能够在一个干净的方式。

希望这有助于您理解斐波那契示例。

现在,转到您的代码,您没有等待匿名 goroutine 完成,事实上,它甚至没有完成。您的“b”方法正在立即发送“1,2,3”和 returns。

因为它是主函数的最后一部分,所以程序终止。

如果您只看到“1,2”,有时是因为“fmt.Println”语句太慢,程序在打印 3 之前终止。

首先,在 func fibonacci 中,select 语句尝试 select 完成以下两项中的第一件事:

  1. c <- x
  2. <- quit

<- quit 很容易理解,它试图从名为 quit 的通道接收值(并忽略接收到的值)。

c <- x 表示发送一个等于(是 x 的副本)的值。看起来像解阻塞,但在 Go 中,当没有接收者时,通过无缓冲通道发送(在 Go 之旅中有解释)会阻塞。

所以这里的意思是,等待接收器准备好接收值(或者缓冲区中的 space,如果它是缓冲通道),在这段代码中意味着 fmt.Println(<-c),然后将值发送给接收方。

因此,只要 <-c 被求值,这个语句就会解除阻塞(结束)。也就是说,循环的每次迭代。

对于您的代码,虽然所有值 123 都保证通过通道发送(并接收),func b returns 因此 func main 保留但不保证 fmt.Println(3) 完成。

在 Go 中,当 func main returns 时,程序终止,未完成的 goroutine 没有机会完成它的工作 - 因此有时它会打印 3 有时它会没有。