1 回答

TA贡献1784条经验 获得超8个赞
一个循环只重复非阻塞检查直到它成功会导致难以诊断的问题(至少,它会过度使用 CPU);使用阻塞检查可以修复它。
我不太确定你的案子的细节。我写了一个像你这样的循环,它在 Playground 上始终挂起“进程花费太长时间”,但是当我在本地运行它时,它确实完成了。
正如我所评论的,我的目标是更简单的设计。
Go 仅对运行中的 goroutine 具有有限的抢占权:运行中的线程仅在发生阻塞操作(如 I/O 或通道操作或等待获取锁)时才将控制权交给 goroutine 调度程序。
因此GOMAXPROCS=1,如果(一个)正在运行的线程开始循环,则其他任何东西都不一定有机会运行。
for { select { ...default: } }因此,A可以开始循环检查通道中的项目,但永远不会放弃对主线程的控制,以便另一个 goroutine 可以写入项目。当GOMAXPROCS超过 1 时,其他代码无论如何都会运行,但不会像 App Engine(或 Playground)上的 1 那样运行。行为不仅取决于GOMAXPROCS,还取决于哪个 goroutine 首先运行,这不一定定义。
为了避免这种情况,删除default:soselect是一个阻塞操作,当调度程序无法接收项目时,它会产生阻塞操作,允许其他代码运行。您可以将其推广到其他可能循环进行非阻塞检查的情况;当阻塞调用不会时,它们中的任何一个都可以使资源忙于不断地重新检查。当GOMAXPROCS>1或运行时的有限抢占拯救您时,轮询(如调用重复检查)仍然可以比阻塞消耗更多的 CPU。
例如,这在 Playground 上因“过程花费太长时间”而失败,尽管烦人的是它在我的机器上可靠地完成:
package main
import "fmt"
func main() {
c := make(chan struct{})
go func() { c <- struct{}{} }()
for {
select {
case <-c:
fmt.Println("success")
return
default:
}
}
}
我不知道是否还有其他问题,但与样本相似的模式的挂起值得注意。
- 1 回答
- 0 关注
- 185 浏览
添加回答
举报