去教程选择语句

时间:2016-01-21 18:03:42

标签: go switch-statement channel

我正在浏览tour.golang.org上的示例,我遇到过这个我真不理解的代码:

package main
import "fmt"

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

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

我理解通道如何工作的基础知识,但我没有得到的是上面的select语句是如何工作的。该教程的解释说:

“select语句允许goroutine等待多个通信操作。 一个选择块直到其中一个案例可以运行,然后执行该案例。如果有多个准备就绪,它会随机选择一个。“

但案件如何执行?据我所知,他们说:

案例:将x发送到频道c

案例:从退出接收

我想我明白第二个只有当quit有一个值时才会执行,这个函数稍后会在go func()中完成。但是检查的第一个案例是什么?另外,在go func()中,我们显然是从c打印值,但c在那个时候不应该有任何东西?我能想到的唯一解释是go func()以某种方式在调用fibonacci()之后执行。我猜它是一个我不完全理解的goroutine,它看起来就像魔术一样。

如果有人能够通过这段代码告诉我它在做什么,我会很感激。

3 个答案:

答案 0 :(得分:8)

请记住,通道会阻塞,因此select语句显示为:

select {
case c <- x: // if I can send to c
    // update my variables
    x, y = y, x+y
case <-quit: // If I can receive from quit then I'm supposed to exit
    fmt.Println("quit")
    return
}

缺少default个案意味着“如果我不能发送到c而且我无法从退出中读取,请阻止直到我可以。”

然后在您的主要过程中,您将关闭另一个从c读取的函数来打印结果

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

这里的关键是要记住频道阻塞,而你正在使用两个无缓冲的频道。使用go分拆第二个功能可让您使用c,因此fibonacci会继续使用。

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

fibonacci生成值并将它们发送到c,并且从main生成的匿名goroutine消耗来自c的值并处理它们(在这种情况下,“处理它们”只是意味着打印到屏幕上)。我们不能简单地生成所有值然后使用它们,因为c将阻止。此外fibonacci将永远产生更多的值(或者直到整数溢出),所以即使你有一个具有无限长缓冲区的魔术通道,它也永远不会传递给消费者。

答案 1 :(得分:1)

了解此代码示例有两点关键:

首先,让我们回顾一下无缓冲通道的工作方式。从documentation

  

如果通道未缓冲,则发送方阻塞,直到接收方收到   收到了价值。

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

第二,当我们使用go关键字启动新的goroutine时,执行将与其他例程并行进行。因此,在示例中,我们运行了两个go例程:由func main()开始的例程,和go func()...内部的func main()开始的例程。

我在此处添加了一些内联注释,这些注释应该使事情更清楚:     包主     导入“ 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 <- 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 <-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("quit")
            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 < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit) // this doesn't start with the go keyword, so it will run on the go routine started by func main()
}

答案 2 :(得分:0)

你几乎得到了它。

go func()中的

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

是的,go关键字启动goroutine,因此 func()将与 fibonacci(c,quit)同时运行。 Println中通道的接收只会阻塞,直到有东西要接收