【问题标题】:Sending value to a channel is not ready in the select在选择中未准备好向通道发送值
【发布时间】:2020-08-17 17:04:32
【问题描述】:
package main

import (
    "fmt"
    "time"
)

func main() {

    ch := make(chan int)
    go func() {
        fmt.Printf("func at %d\n", time.Now().UnixNano())
        select {
        case ch <- 1:
            fmt.Println("running send")
        default:
            fmt.Println("running default")
        }
    }()

    time.Sleep(100 * time.Millisecond)
    fmt.Printf("main at %d\n", time.Now().UnixNano())
    fmt.Println(<-ch)
}

the playground here

研究了一天还是无法解释为什么case ch &lt;- 1:没有准备好,选择默认情况下运行。当然会导致死锁!

【问题讨论】:

  • 如果您正在等待可能稍后出现的值,为什么您有 default 案例?

标签: go deadlock channel goroutine


【解决方案1】:

来自doc

如果通道没有缓冲,发送方会阻塞,直到接收方收到该值。如果通道有缓冲区,发送方只会阻塞,直到值被复制到缓冲区;如果缓冲区已满,这意味着等待某个接收器检索到一个值。

一种方法是使用它。这是接收器 goroutine 首先生成的。此外,如果接收器尚未准备好,则会选择默认情况。如果它准备好了,具体案例也将准备好。如果您多次运行此程序,您可以看到任何一种情况发生。

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan int)
    // goroutine acts as the reciever
    go func() {
        fmt.Printf("main at %d\n", time.Now().UnixNano())
        fmt.Println(<-ch)
    }()
    go func() {
        fmt.Printf("func at %d\n", time.Now().UnixNano())
        select {
        case ch <- 1:
            fmt.Println("running send")
        default:
            fmt.Println("running default")
        }
    }()
    time.Sleep(1 * time.Second) // Wait for the goroutines
}

另一种解决方案是使用缓冲通道:

package main

import (
    "fmt"
    "time"
)

func main() {

    ch := make(chan int, 1)
    go func() {
        fmt.Printf("func at %d\n", time.Now().UnixNano())
        select {
        case ch <- 1:
            fmt.Println("running send")
        default:
            fmt.Println("running default")
        }
    }()

    time.Sleep(100 * time.Millisecond)
    fmt.Printf("main at %d\n", time.Now().UnixNano())
    fmt.Println(<-ch)
}

另外,请在 stackoverflow 上阅读 thread

【讨论】:

  • 据我了解,case ch &lt;- 1default: 都已准备就绪,请随机选择default:。这就是fmt.Println(&lt;-ch) 导致死锁的原因。是你的意思吗?
  • 是和不是。要点在于您的代码case ch &lt;- 1 没有准备好,因为我给出的解释与无缓冲通道有关。是的,选择默认值和使用fmt.Println(&lt;-ch) 会造成死锁状态。
  • 我已经编辑了答案。请看一看。并多次运行第一个程序以查看这两种情况。您将了解生成 goroutine 的重要性。
  • 看,你的频道是无缓冲的,这意味着如果发送者发送到频道,没有缓冲区来存储它,因此应该有一个接收者准备好。拥有一个缓冲通道将保证它的通道有一些缓冲区来存储它。因此,发送可能会失败,因为没有人准备好,这意味着如果结果在主 goroutine 到达接收之前到达,发送可能会失败。
【解决方案2】:

您已使用未缓冲的 make(chan int) 创建了 Go 通道。您需要一个缓冲通道(不一定会阻塞),您应该使用 make(chan int, 20) 其中 20 是通道的大小

关于无缓冲通道的问题是它们也是同步的,因此它们总是在写入和读取时阻塞。当缓冲区填满时,缓冲通道也会发生同样的事情。

试试下面的代码

package main

import (
    "fmt"
    "time"
)

func main() {

    ch := make(chan int, 20)
    go func() {
        fmt.Printf("func at %d\n", time.Now().UnixNano())
        select {
        case ch <- 1:
            fmt.Println("running send")
        default:
            fmt.Println("running default")
        }
    }()

    time.Sleep(100 * time.Millisecond)
    fmt.Printf("main at %d\n", time.Now().UnixNano())
    fmt.Println(<-ch)
}

【讨论】:

  • 你能解释一下吗?我知道缓冲通道可以解决这个问题,我也知道缓冲和非缓冲通道的锁定技术。但是,我无法解释这里发生了什么。
猜你喜欢
  • 2021-01-12
  • 1970-01-01
  • 2020-03-10
  • 2023-01-05
  • 2020-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多