英文:
Go scheduler and CGO: Please explain this difference of behavior?
问题
我想知道这个代码的实现原因:
package main
func main() {
c := make(chan struct{})
go func() {
print("a")
for {
}
}()
go func() {
print("b")
for {
}
}()
go func() {
print("c")
c <- struct{}{}
for {
}
}()
<-c
}
GOMAXPROCS=2 go run sample1.go
ab <--- 阻塞。
package main
// static void loop() { for(;;); }
import "C"
func main() {
c := make(chan struct{})
go func() {
print("a")
C.loop()
print("x")
}()
go func() {
print("b")
C.loop()
print("y")
}()
go func() {
print("c")
c <- struct{}{}
C.loop()
print("z")
}()
<-c
}
GOMAXPROCS=2 go run sample2.go
abc <--- 优雅地结束。
更具体地说,我想知道在 go routine 调度的上下文中,C 的紧密循环与 Go 的循环有何不同。尽管在 Go 程序结束时 C 的紧密循环应该被突然终止,但我想知道是否可以安全地依赖这种行为来启动 C 任务而不阻塞 Go 程序。
<details>
<summary>英文:</summary>
I'd like to know the implementation reason for this:
package main
func main() {
c := make(chan struct{})
go func() {
print("a")
for {
}
}()
go func() {
print("b")
for {
}
}()
go func() {
print("c")
c <- struct{}{}
for {
}
}()
<-c
}
❯❯❯ GOMAXPROCS=2 go run sample1.go
ab <--- blocks.
---
package main
// static void loop() { for(;;); }
import "C"
func main() {
c := make(chan struct{})
go func() {
print("a")
C.loop()
print("x")
}()
go func() {
print("b")
C.loop()
print("y")
}()
go func() {
print("c")
c <- struct{}{}
C.loop()
print("z")
}()
<-c
}
❯❯❯ GOMAXPROCS=2 go run sample2.go
abc <--- ends gracefully.
More specifically I mean how the C tight loop differs from the Go one in the context of go routine scheduling. And even though the C tight loops should be being abruptly terminated at the end of the Go program, I'd like to know whether it's safe to rely on this behavior to start C tasks without blocking the Go program.
</details>
# 答案1
**得分**: 4
运行时无法抢占真正的忙循环。一个没有调度点的CPU密集型循环必须在自己的线程中运行,以便其他goroutine能够运行。函数调用和通道的发送或接收操作都会让出执行权。网络IO是异步调度的,文件IO有自己的线程。
通常设置`GOMAXPROCS` > 1就足够了,但由于你有3个这样的循环,而只有2个线程,调度器仍然被阻塞。如果你有一个有效的CPU密集型循环,使得调度goroutine变得困难,你可以定期调用`runtime.GoSched()`来让出执行权给调度器。在现实世界中,这通常只在编程错误的情况下才会发生,即出现了一个空循环。
所有的cgo调用都在go运行时之外的它们自己的线程中进行,所以这些循环对主循环没有影响,除了浪费CPU。
<details>
<summary>英文:</summary>
The runtime can't preempt a true busy-loop. A CPU intensive loop with no scheduling points has to be in it's own thread for other goroutines to be able to run. Function calls and channel send or receive operations all yield. Network IO is scheduled asynchronously, and file IO gets its own thread.
Generally setting `GOMAXPROCS` > 1 is sufficient, but since you have 3 of these loops, and only 2 threads, the scheduler is still blocked. If you have a valid CPU intensive loop that is making it difficult to schedule goroutines, you can call `runtime.GoSched()` periodically to yield to the scheduler. In the real world, the only place where this generally maters is a programming error where you have an empty loop.
All cgo calls happen in their own threads outside of the go runtime, so those loops have no effect on the main loop, other than wasting CPU.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论