【问题标题】:Select on a go send and receive channel at the same time同时选择发送和接收通道
【发布时间】:2015-03-25 21:52:00
【问题描述】:

假设我有一个缓冲的发送和非缓冲的接收通道:

s := make(chan<- int, 5)
r := make(<-chan int)

是否可以在两者上都使用select,这样r 如果有要阅读的内容就会被选中,而s 如果没有读满就会被选中?与此等价的东西,但不使用 100% CPU:

for {
    if len(s) < cap(s) {
        // Send something
    }
    if len(r) > 0 {
        // Receive something
    }
}

请注意,我想在发送时决定发送什么,而不是更早。

编辑

这个问题基本上相当于“我可以阻塞直到一个频道准备好发送,而不发送任何东西?”

【问题讨论】:

    标签: select go channel


    【解决方案1】:

    这是一个简单的选择:

    select {
    case s <- n:
        // Successful send.
    case n := <- r:
        // Successful receive. Do something with n.
    }
    

    【讨论】:

    • 但是何时评估s &lt;- n?例如,如果我想通过频道发送当前时间怎么办。
    • @Timmmm 它在Spec 中。 send 语句的右侧表达式只计算一次,在输入“select”语句时。
    【解决方案2】:

    您可以使用select 执行此操作,但由于要发送的值只评估一次,如果两个通道都未准备好,则要发送的值将在可以发送时变得过时。

    所以添加一个default 案例,如果没有任何通道准备好,则将执行该案例,您只需“睡眠”一点,然后重试(计算/获取要发送的更新的新值)。通过休眠,您不会消耗 CPU 资源:

    s := make(chan<- int, 5)
    r := make(<-chan int)
    
    for {
        v := valueToSend() // Evaluated each time we try to send
        select {
        case s <- v:
            fmt.Println("Sent value:", v)
        case vr := <-r:
            fmt.Println("Received:", vr)
        default: // If none are ready currently, we end up here
            time.Sleep(time.Millisecond * 1)
        }
    }
    

    注意检查通道的长度或容量然后发送/接收不是一个好的解决方案,因为通道可能在您检查的时间之间没有准备好它的长度/上限,您尝试发送/接收,如下图所示:

    if len(r) > 0 {
        // r is ready to receive
    
        // Optional other code here,
        // meanwhile another goroutine might receive the value from r!
    
        r <-  // If other goroutine received from r, this will block!
    }
    

    【讨论】:

    • 是的,虽然Sleep(1ms) 解决方案有效,但它看起来并不优雅,它肯定会让 CPU 保持清醒。只要我知道我是唯一的发件人,在发送前检查长度肯定没问题。
    • @Timmmm 是的,使用Sleep 并不优雅,但是您尝试实现的目标无法以其他方式完成,因为您希望在当时确定/计算值 i> 通道准备就绪。如果这不是一个要求,一个简单的select 就可以了。
    • 另外请注意,如果 2 个通道不相关,则可以只使用 2 个 goroutine,一个发送,另一个接收。如果通道未就绪,则操作将简单地阻塞而不消耗 CPU 资源。
    • 您的解决方案也每毫秒调用一次valueToSend()。对我来说,这是一个稍微昂贵的操作,只应在必要时调用。事实上,我原来的答案加上time.Sleep() 更好。
    【解决方案3】:

    我可以阻止直到频道准备好发送,而不发送任何内容?

    不适用于原始的 go 通道。您可能可以使用我的频道库中的 SharedBuffer 类型将一些东西组合在一起,但即使这样也很复杂,而且它在幕后使用了大量的反射。

    https://godoc.org/github.com/eapache/channels#SharedBuffer

    【讨论】:

      【解决方案4】:

      您可以发送一个可以计算该值的对象,而不是直接发送值。然后您可以检测对象何时发送,然后进行计算。您可以使用 sync.Once 来确保计算只完成一次,并对结果进行门控访问。这样可以避免使用睡眠。

      类似这样的:https://play.golang.org/p/oL2HA2jl91

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多