【发布时间】:2021-06-15 18:02:59
【问题描述】:
我正在阅读 O'Reilly 的 Go 并发书,并找到了以下代码示例:
doWork := func(
done <-chan interface{},
pulseInterval time.Duration,
) (<-chan interface{}, <-chan time.Time) {
heartbeat := make(chan interface{})
results := make(chan time.Time)
go func() {
defer close(heartbeat)
defer close(results)
pulse := time.Tick(pulseInterval)
workGen := time.Tick(2 * pulseInterval) // this just creates workload
sendPulse := func() {
select {
case heartbeat <- struct{}{}:
default:
}
}
sendResult := func(r time.Time) {
for {
select {
case <-done:
return
case <-pulse:
sendPulse()
case results <- r:
return
}
}
}
for {
select {
case <-done:
return
case <-pulse:
sendPulse()
case r := <-workGen:
sendResult(r)
}
}
}()
return heartbeat, results
}
两个 select 语句被用于一个简单的心跳似乎很奇怪。然后我明白了这背后的原因是 sendResults 不应该阻止尝试将结果发送到results 频道。如果没有人从该通道接收,它将有效地阻塞 goroutine 并停止发送心跳。
然后我想……他们为什么不这样编码呢?
doWork := func(
done <-chan interface{},
pulseInterval time.Duration,
) (<-chan interface{}, <-chan time.Time) {
heartbeat := make(chan interface{})
results := make(chan time.Time)
go func() {
defer close(heartbeat)
defer close(results)
pulse := time.Tick(pulseInterval)
workGen := time.Tick(2 * pulseInterval) // this just creates workload
sendPulse := func() {
select {
case heartbeat <- struct{}{}:
default:
}
}
for {
select {
case <-done:
return
case <-pulse:
sendPulse()
case results <- <- workgen:
fmt.Println("Send result")
}
}
}()
return heartbeat, results
}
并意识到这是因为如果 workgen 有一个可用的项目并且我阅读了它但没有人从结果中读取,那么 workgen 项目将会丢失。
问题
是否有任何其他模式可以避免这些必须重复所有情况才能不遗漏值的嵌套选择?
想法
如果一个 select 语句首先验证结果通道正在等待接收一个值而不是先执行右手表达式,那么难道所有这些问题都不会得到解决吗?
换句话说,如果我有这个results <- <- workgen,如果选择说“嘿,没有人从结果中读取,我们不要尝试评估
我认为一个案例应该首先尝试评估表达式中的通道是否准备好接收或发送数据,然后执行表达式。
【问题讨论】:
-
它会释放工作项并且不会阻塞 select 语句。我编写了代码并对其进行了测试。请问可以验证吗?我很确定我测试了什么。
-
这种模式
results <- <- workgen不会按照你的意愿工作,因为 select 语句只会关注results <- x部分,而<-workgen结果将被立即评估(并被丢弃如果results还没有准备好)。见Both receive and send in same select case -
“一个案例应该首先尝试评估表达式中的通道是否准备好接收或发送数据”。问题是这些信息只有在连贯时才有用。如果您看到如果您看到 A 已准备好,然后您看到 B 已准备好,那并不一定意味着 A 和 B 已经或曾经同时准备好。运行时理论上可以解决这个问题,但解决方案可能过于复杂或在调度程序中产生大量噪音。
-
@HymnsForDisco 耶哈,你是对的。该问题的解决方案将非常复杂。另外,假设表达式中的所有通道都准备好了。在评估子表达式时,可能一个或多个通道不再可用(准备好读取和发送)。
标签: go concurrency