【问题标题】:What is the correct use of select statement in Go?Go中select语句的正确用法是什么?
【发布时间】:2018-05-29 07:45:05
【问题描述】:

大家好

我一直在学习 Go 的基础知识以及如何使用其基于通道的并发范式。

但是,在使用我编写的一些专注于 select 语句的代码时,我发现了一个奇怪的行为:

func main() {
    even := make(chan int)
    odd := make(chan int)
    quit := make(chan bool)

    //send
    go send(even, odd, quit)

    //receive
    receive(even, odd, quit)
    fmt.Println("Exiting")
}

func send(e, o chan<- int, q chan<- bool) {
    for i := 0; i < 100; i++ {
        if i%2 == 0 {
            e <- i
        } else {
            o <- i
        }
    }
    close(e)
    close(o)
    q <- true
    close(q)
}

func receive(e, o <-chan int, q <-chan bool) {
    for cont, i := true, 0; cont; i++ {
        fmt.Println("value of i", i, cont)
        select {
        case v := <-e:
            fmt.Println("From even channel:", v)
        case v := <-o:
            fmt.Println("from odd channel:", v)
        case v := <-q:
            fmt.Println("Got exit message", v)
            // return // have also tried this instead
            cont = false
        }
    }
}

当我运行这个简单的程序时,有时 i 累加器最终会在控制台上打印超过 100,而不是以“来自奇数通道:99”结束,for 循环继续输出一个或多个偶数/奇数通道随机归零,好像退出通道的消息在某种程度上被延迟到它的情况下,而是奇数/偶数通道发送更多整数,因此在奇数/偶数通道关闭后不完全退出 for 循环。

value of i 97 true
from odd channel: 97
value of i 98 true
From even channel: 98
value of i 99 true
from odd channel: 99
value of i 100 true
From even channel: 0
value of i 101 true
From even channel: 0
value of i 102 true
from odd channel: 0
value of i 103 true
From even channel: 0
value of i 104 true
Got exit message true
Exiting

我已尝试搜索 case 语句的正确用法,但我无法找到我的代码的问题。

似乎可以在 go playground 上重现相同的行为:my code

感谢您对我的问题的关注。

【问题讨论】:

  • 好吧,我发现完全删除 close() 函数会使程序的行为符合我的预期,这种函数仍然是一个非常有趣的行为。
  • 我建议你收藏Channel Axioms by Dave Cheney。当我学习这些东西时,它对我帮助很大。

标签: select go concurrency


【解决方案1】:

程序正在打印 0,因为在关闭的通道上接收返回零值。这是实现目标的一种方法。

首先,消除q 频道。关闭oe 通道就足以表明发送方已完成。

func send(e, o chan<- int, q chan<- bool) {
    for i := 0; i < 100; i++ {
        if i%2 == 0 {
            e <- i
        } else {
            o <- i
        }
    }
    close(e)
    close(o)
}

在接收值时,使用两个值receive来检测何时返回零值,因为通道关闭。当通道关闭时,将通道设置为 nil。在 nil 通道上接收不会产生值。循环直到两个通道都为零。

func receive(e, o <-chan int, q <-chan bool) {

    for e != nil && o != nil {
        select {
        case v, ok := <-e:
            if !ok {
                e = nil
                continue
            }
            fmt.Println("From even channel:", v)
        case v, ok := <-o:
            if !ok {
                o = nil
                continue
            }
            fmt.Println("From odd channel:", v)
        }
    }
}

playground example

【讨论】:

  • 很好,当从一个指示该通道是否打开的通道中检索时,我不知道除了发送的值本身之外还有第二个值。谢谢!
  • 请注意,第二个值并不表示通道是否关闭,它表示(第一个)值是否为零值,因为通道已关闭。如果你有一个缓冲的通道,如果通道的缓冲中仍有数据,即使通道关闭,该布尔值仍然为假。示例:play.golang.org/p/T6unYxQtZE
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-05
  • 1970-01-01
  • 2015-11-21
  • 2013-01-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多