同时选择一个发送和接收通道

huangapple go评论86阅读模式
英文:

Select on a go send and receive channel at the same time

问题

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

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

是否可以在它们上面使用select语句,以便如果r有可读内容,则选择r,如果s未满,则选择s?类似于下面的代码,但不会占用100%的CPU:

for {
    if len(s) < cap(s) {
        // 发送一些内容
    }
    if len(r) > 0 {
        // 接收一些内容
    }
}

请注意,我希望在发送时决定发送什么内容,而不是提前决定。

编辑

这个问题基本上等同于“我可以在通道准备好发送之前阻塞吗,而不发送任何内容?”

英文:

Suppose I have a buffered send and unbuffered receive channel:

s := make(chan&lt;- int, 5)
r := make(&lt;-chan int)

Is it possible to select on them both, so that r will be selected if it has anything to read, and s will be selected if it is not full? Something equivalent to this, but not using 100% CPU:

for {
    if len(s) &lt; cap(s) {
        // Send something
    }
    if len(r) &gt; 0 {
        // Receive something
    }
}

Note that I want to decide what to send at the time that I send it, not earlier.

Edit

This question is basically equivalent to "Can I block until a channel is ready-to-send, without sending anything?"

答案1

得分: 4

你可以使用select来实现这个功能,但是由于要发送的值只会被评估一次,如果两个通道都没有准备好,那么要发送的值在可以发送的时候可能已经过时了。

因此,添加一个default分支,如果没有任何通道准备好,就执行该分支,在其中稍微"休眠"一下,然后再尝试(使用计算/获取的更新值进行发送)。通过休眠,你将不会消耗CPU资源:

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

for {
    v := valueToSend() // 每次尝试发送时都会重新评估
    select {
    case s <- v:
        fmt.Println("Sent value:", v)
    case vr := <-r:
        fmt.Println("Received:", vr)
    default: // 如果当前没有任何通道准备好,就会执行这里
        time.Sleep(time.Millisecond * 1)
    }
}

注意,检查通道的长度或容量,然后再发送/接收,不被认为是一个好的解决方案,因为在你检查其长度/容量和尝试发送/接收之间,通道可能变得不可用,如下所示:

if len(r) > 0 {
    // r 准备好接收

    // 可选的其他代码在这里,
    // 与此同时,另一个 goroutine 可能会从 r 接收到值!

    r <- // 如果其他 goroutine 从 r 接收到值,这里将会阻塞!
}
英文:

You can do this with select but since the value to be sent is evaluated only once, if both channel are not ready, the value to be sent would become outdated by the time it can be sent.

So add a default case which will be executed if none of the channels are ready, in which you just "sleep" a little, then try again (with an updated new value calculated/acquired to be sent). By sleeping you will not consume CPU resources:

s := make(chan&lt;- int, 5)
r := make(&lt;-chan int)

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

Note that checking the length or capacity of a channel and then sending/receiving is not considered a good solution because the channel might become not ready between the time you check its length/cap and you try to send/receive, as illustrated below:

if len(r) &gt; 0 {
    // r is ready to receive

    // Optional other code here,
    // meanwhile another goroutine might receive the value from r!

    r &lt;-  // If other goroutine received from r, this will block!
}

答案2

得分: 1

这是一个简单的选择语句:

select {
case s <- n:
    // 发送成功。
case n := <- r:
    // 接收成功。对 n 做一些操作。
}
英文:

It's a simple select:

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

答案3

得分: 1

你可以发送一个能够计算值的对象,而不是直接发送值。然后,你可以检测到对象的发送,并进行计算。你可以使用sync.Once来确保计算只执行一次,并限制对结果的访问。这样可以避免使用Sleep。

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

英文:

Instead of sending the value directly, you could send an object which can compute the value. Then you can detect when the object is sent, and then compute. You can use sync.Once to make sure the computation is done once, and gate access to the result. This avoids using Sleeps.

Something like this: https://play.golang.org/p/oL2HA2jl91

答案4

得分: 0

你可以使用原始的Go通道来阻塞,直到通道准备好发送数据。但是,如果你不发送任何内容,是无法实现的。你可以尝试使用我的通道库中的SharedBuffer类型来实现,但即使这样也会比较复杂,并且在内部使用了大量的反射。

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

英文:

> Can I block until a channel is ready-to-send, without sending anything?

Not with primitive go channels. You could probably manage to pull something together using the SharedBuffer type in my channels library, but even that is complicated and it uses a great deal of reflection under the covers.

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

huangapple
  • 本文由 发表于 2015年1月27日 18:08:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/28167826.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定