【问题标题】:In the Go select construct, can I have send and receive to unbuffered channel in two case statements?在 Go select 构造中,我可以在两个 case 语句中发送和接收到无缓冲通道吗?
【发布时间】:2020-03-19 21:21:11
【问题描述】:

我希望这段代码进入一个无限循环,发送和接收消息。但它似乎既不发送也不接收。为什么?

go func() {
    for {
        select {
        case ch1 <- 1:
            println("sent 1 ")
        case c := <-ch1:
            println(" received ", c)
        }
        time.Sleep(time.Second)
    }
}()

【问题讨论】:

    标签: go


    【解决方案1】:

    channel documentation says:

    如果容量为零或不存在,则通道是无缓冲的,并且只有在发送方和接收方都准备好时通信才会成功。

    select documentation says:

    如果一个或多个通信可以继续,则通过统一的伪随机选择选择一个可以继续的通信。

    接收案例无法继续,因为没有准备好的发送者。发送案例无法继续,因为没有准备好的接收者。程序在选择中死锁。

    单个 goroutine 无法使发送和接收都准备好,因为 goroutine 一次只能执行 select 的一个分支。

    如果通道被缓冲,程序将永远循环 (playground example)。

    【讨论】:

    • 为什么这个语句 :"case c := 建议的下面的例子
    • 单个 goroutine 无法使发送和接收都准备好,因为 goroutine 一次只能执行一个 select 分支。
    【解决方案2】:

    对于具有一个 goroutine 的无缓冲通道:(原因:按设计):
    您可以使用缓冲通道和一个 goroutine
    两个使用非缓冲通道的 goroutine 以避免死锁

    如果通道没有缓冲,发送方会阻塞,直到接收方有 收到了价值。如果通道有缓冲区,则发送方阻塞 仅在将值复制到缓冲区之前;如果缓冲区是 已满,这意味着等到某个接收器检索到一个值。


    1- 使用 缓冲通道main 本身就是一个 goroutine,如果您需要在一个 goroutine 中执行此操作,您应该像这样使用缓冲通道:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        ch1 := make(chan int, 1)
        for {
            select {
            case ch1 <- 1:
                fmt.Println("sent 1 ")
            case c := <-ch1:
                fmt.Println(" received ", c)
            }
            time.Sleep(time.Second)
        }
    }
    

    输出:

    sent 1
     received  1
    sent 1
     received  1
    sent 1
     received  1
    

    2- 使用无缓冲通道:您可以在一个select 中的两个case 语句中发送和接收到无缓冲通道,您需要两个goroutines,见:

    试试this:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        go ct("Alex")
        go ct("John")
        //select {}
        time.Sleep(300 * time.Millisecond)
    }
    func ct(name string) {
        for {
            select {
            case ch1 <- 1:
                fmt.Println(name, "sent 1")
            case c := <-ch1:
                fmt.Println(name, "received:", c)
            }
            fmt.Println(name, "waiting...")
            time.Sleep(100 * time.Millisecond)
        }
    }
    
    var ch1 = make(chan int)
    

    输出:

    John sent 1
    John waiting...
    Alex received: 1
    Alex waiting...
    John received: 1
    John waiting...
    Alex sent 1
    Alex waiting...
    John sent 1
    John waiting...
    Alex received: 1
    Alex waiting...
    

    【讨论】:

    • 我的意思是为什么我不能像上面那样使用单个 go 例程来做到这一点。
    • @A-R - 感谢您的编辑,但实际上 - 我并没有试图让它工作 - 我试图理解其中的原因。
    • 为什么这个语句 :"case c :=
    • 这是设计使然,这很好,在许多情况下,我们需要无缓冲的通道来进行直接握手。当我们需要在不直接握手的情况下将数据发送到另一个通道时,我们可以使用缓冲通道,因此我们不在乎对方何时接收数据,在您的代码中,此代码首先运行:case ch1 &lt;- 1 并且它需要握手,所以只需使用缓冲通道,然后我们就可以开始了!在select 中所有case 语句自上而下运行一次,而不是同时运行:尝试this
    猜你喜欢
    • 2016-01-24
    • 2021-10-06
    • 1970-01-01
    • 2013-04-13
    • 1970-01-01
    • 1970-01-01
    • 2018-10-02
    • 2019-01-17
    相关资源
    最近更新 更多