Go教程:选择语句(select statement)

huangapple go评论78阅读模式
英文:

go tutorial select statement

问题

我正在阅读tour.golang.org上的示例,并遇到了这段我不太理解的代码:

package main
import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x: // case: 将 x 发送到通道 c?
            x, y = y, x+y
        case <-quit: // 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)
}

我理解通道的基本工作原理,但我不明白上面的 select 语句是如何工作的。教程上的解释是:

"select 语句让一个 goroutine 可以等待多个通信操作。
select 会阻塞,直到其中一个 case 可以运行,然后它就执行那个 case。如果有多个 case 都准备好了,它会随机选择一个。"

但是这些 case 是如何被执行的呢?据我所知,它们的意思是:

case: 将 x 发送到通道 c

case: 从 quit 接收

我想我理解第二个 case 只有在 quit 有值的情况下才会执行,这是在 go func() 中稍后完成的。但第一个 case 检查的是什么?此外,在 go func() 中,我们显然从 c 中打印值,但此时 c 应该是空的吧?我唯一能想到的解释是 go func() 在调用 fibonacci() 后以某种方式执行。我猜它是一个 goroutine,我对 goroutine 的理解还不够深入,它似乎就像是魔法一样。

如果有人能够解释一下这段代码在做什么,我将不胜感激。

英文:

I'm working through the examples at tour.golang.org, and I've encountered this code I don't really understand:

package main
import &quot;fmt&quot;

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c &lt;- x: // case: send x to channel c?
			x, y = y, x+y
		case &lt;-quit: // case: receive from channel quit?
			fmt.Println(&quot;quit&quot;)
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() { // when does this get called?
		for i := 0; i &lt; 10; i++ {
			fmt.Println(&lt;-c)
		}
		quit &lt;- 0
	}()
	fibonacci(c, quit)
}

I understand the basics of how channels work, but what I don't get is how the above select statement works. The explanation on the tutorial says:

"The select statement lets a goroutine wait on multiple communication operations.
A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready."

But how are the cases getting executed? From what I can tell, they're saying:

case: send x to channel c

case: receive from quit

I think I understand that the second one executes only if quit has a value, which is done later inside the go func(). But what is the first case checking for? Also, inside the go func(), we're apparently printing values from c, but c shouldn't have anything in it at that point? The only explanation I can think of is that the go func() somehow executes after the call to fibonacci(). I'm guessing it's a goroutine which I don't fully understand either, it just seems like magic.

I'd appreciate if someone could go through this code and tell me what it's doing.

答案1

得分: 18

请记住,通道会阻塞,因此选择语句如下所示:

select {
case c <- x: // 如果我可以发送到 c
    // 更新变量
    x, y = y, x+y
case <-quit: // 如果我可以从 quit 接收到数据,那么我应该退出
    fmt.Println("quit")
    return
}

缺少 default 语句意味着“如果我不能发送到 c 并且我不能从 quit 读取数据,则阻塞直到可以为止。”

然后在主进程中,你可以启动另一个函数来从 c 读取并打印结果:

for i := 0; i < 10; i++ {
    fmt.Println(<-c) // 从 c 中读取数据
}
quit <- 0 // 发送数据到 quit 来终止主进程。

关键在于记住通道会阻塞,并且你正在使用两个无缓冲通道。使用 go 来启动第二个函数可以从 c 中消费数据,这样 fibonacci 函数就可以继续执行。


Goroutines 被称为“绿色线程”。使用关键字 go 启动的函数调用会在一个新的进程中运行,独立于主线程的执行。实际上,main()go func() ... 是同时运行的!这很重要,因为我们在这段代码中使用了生产者/消费者模式。

fibonacci 函数生成值并将其发送到 c,而从主函数中生成的匿名 goroutine 则从 c 中消费值并进行处理(在这种情况下,处理值只是将其打印到屏幕上)。我们不能简单地生成所有的值然后再消费它们,因为 c 会阻塞。此外,fibonacci 函数将无限地生成更多的值(或者直到整数溢出为止),所以即使你有一个具有无限缓冲区的神奇通道,它也永远不会被消费者获取到。

英文:

Remember that channels will block, so the select statement reads:

select {
case c &lt;- x: // if I can send to c
    // update my variables
    x, y = y, x+y
case &lt;-quit: // If I can receive from quit then I&#39;m supposed to exit
    fmt.Println(&quot;quit&quot;)
    return
}

The absence of a default case means "If I can't send to c and I can't read from quit, block until I can."

Then in your main process you spin off another function that reads from c to print the results

for i:=0; i&lt;10; i++ {
    fmt.Println(&lt;-c)  // read in from c
}
quit &lt;- 0  // send to quit to kill the main process.

The key here is to remember that channels block, and you're using two unbuffered channels. Using go to spin off the second function lets you consume from c so fibonacci will continue.


Goroutines are so-called "green threads." Starting a function call with the keyword go spins it off into a new process that runs independent of the main line of execution. In essence, main() and go func() ... are running simultaneously! This is important since we're using a producer/consumer pattern in this code.

fibonacci produces values and sends them to c, and the anonymous goroutine that's spawned from main consumes values from c and processes them (in this case, "processing them" just means printing to the screen). We can't simply produce all the values and then consume them, because c will block. Furthermore fibonacci will produce more values forever (or until integer overflow anyway) so even if you had a magic channel that had an infinitely long buffer, it would never get to the consumer.

答案2

得分: 5

理解这段代码示例有两个关键点:

首先,让我们回顾一下无缓冲通道的工作原理。根据文档

如果通道是无缓冲的,发送者会阻塞,直到接收者接收到值。

请注意,代码示例中的两个通道 cquit 都是无缓冲的。

其次,当我们使用 go 关键字启动一个新的 goroutine 时,执行将与其他例程并行进行。因此,在这个示例中,我们有两个正在运行的 goroutine:由 func main() 启动的例程,以及在 func main() 内部的 go func()... 启动的例程。

我在这里添加了一些内联注释,这应该会使事情更清晰:

package main
import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for { // 这相当于一个没有停止条件的 while 循环
        select {
        case c <- x: // 当我们可以向通道 c 发送数据时,由于 c 是无缓冲的,我们只能在有人尝试从中接收数据时才能发送数据到通道 c
            x, y = y, x+y
        case <-quit: // 当我们可以从通道 quit 接收数据时,由于 quit 是无缓冲的,我们只能在有人尝试向其中发送数据时才能接收数据
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() { // 这在另一个 goroutine 中运行,与主 goroutine 分开
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit) // 这不使用 go 关键字开始,所以它将在由 func main() 启动的 goroutine 上运行
}
英文:

There are two key things to understanding this code example:

First, let's review how unbuffered channels work. From the documentation

> If the channel is unbuffered, the sender blocks until the receiver has
> received the value.

Note that both channels in the code example, c and quit are unbuffered.

Secondly, when we use the go keyword to start a new goroutine, the execution will happen in parallel with other routines. So in the example, we have two go routines running: the routine started by func main(), and the routine started by go func()... inside the func main().

I added some inline comments here which should make things clearer:
package main
import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for { // this is equivalent to a while loop, without a stop condition
        select {
        case c &lt;- x: // when we can send to channel c, and because c is unbuffered, we can only send to channel c when someone tries to receive from it
            x, y = y, x+y
        case &lt;-quit: // when we can receive from channel quit, and because quit is unbuffered, we can only receive from channel quit when someone tries to send to it
            fmt.Println(&quot;quit&quot;)
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() { // this runs in another goroutine, separate from the main goroutine
        for i := 0; i &lt; 10; i++ {
            fmt.Println(&lt;-c)
        }
        quit &lt;- 0
    }()
    fibonacci(c, quit) // this doesn&#39;t start with the go keyword, so it will run on the go routine started by func main()
}

答案3

得分: 0

你基本上已经理解了。

go func()内部,我们显然从c中打印值,但是在那个时候c不应该有任何内容?我唯一能想到的解释是go func()在调用fibonacci()之后以某种方式执行。我猜它是一个goroutine。

是的,go关键字启动了一个goroutine,所以func()将与fibonacci(c, quit)同时运行。在Println中从通道接收只是简单地阻塞,直到有东西可以接收。

英文:

You've pretty much got it.

inside the go func(), we're apparently printing values from c, but c shouldn't have anything in it at that point? The only explanation I can think of is that the go func() somehow executes after the call to fibonacci(). I'm guessing it's a goroutine

Yes, the go keyword starts a goroutine, so the func() will run at the same time as the fibonacci(c, quit). The receive from the channel in the Println simply blocks until there is something to receive

huangapple
  • 本文由 发表于 2016年1月22日 02:03:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/34931059.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定