这个 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 完成以下两项中的第一件事:
c <- x
<- quit
<- quit
很容易理解,它试图从名为 quit
的通道接收值(并忽略接收到的值)。
c <- x
表示发送一个等于(是 x 的副本)的值。看起来像解阻塞,但在 Go 中,当没有接收者时,通过无缓冲通道发送(在 Go 之旅中有解释)会阻塞。
所以这里的意思是,等待接收器准备好接收值(或者缓冲区中的 space,如果它是缓冲通道),在这段代码中意味着 fmt.Println(<-c)
,然后将值发送给接收方。
因此,只要 <-c
被求值,这个语句就会解除阻塞(结束)。也就是说,循环的每次迭代。
对于您的代码,虽然所有值 1
、2
、3
都保证通过通道发送(并接收),func b
returns 因此 func main
保留但不保证 fmt.Println(3)
完成。
在 Go 中,当 func main
returns 时,程序终止,未完成的 goroutine 没有机会完成它的工作 - 因此有时它会打印 3
有时它会没有。
我一直在关注 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 完成以下两项中的第一件事:
c <- x
<- quit
<- quit
很容易理解,它试图从名为 quit
的通道接收值(并忽略接收到的值)。
c <- x
表示发送一个等于(是 x 的副本)的值。看起来像解阻塞,但在 Go 中,当没有接收者时,通过无缓冲通道发送(在 Go 之旅中有解释)会阻塞。
所以这里的意思是,等待接收器准备好接收值(或者缓冲区中的 space,如果它是缓冲通道),在这段代码中意味着 fmt.Println(<-c)
,然后将值发送给接收方。
因此,只要 <-c
被求值,这个语句就会解除阻塞(结束)。也就是说,循环的每次迭代。
对于您的代码,虽然所有值 1
、2
、3
都保证通过通道发送(并接收),func b
returns 因此 func main
保留但不保证 fmt.Println(3)
完成。
在 Go 中,当 func main
returns 时,程序终止,未完成的 goroutine 没有机会完成它的工作 - 因此有时它会打印 3
有时它会没有。