英文:
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<- int, 5)
r := make(<-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) < cap(s) {
// Send something
}
if len(r) > 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<- 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)
}
}
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) > 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!
}
答案2
得分: 1
这是一个简单的选择语句:
select {
case s <- n:
// 发送成功。
case n := <- r:
// 接收成功。对 n 做一些操作。
}
英文:
It's a simple select:
select {
case s <- n:
// Successful send.
case n := <- 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论