Go 调度程序和 CGO:请解释这种行为差异?

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  <--- 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.

更具体地说,我指的是在 go 例程调度的上下文中,C 紧密循环与 Go 紧密循环有何不同。即使 C 紧密循环应该在 Go 程序结束时突然终止,我想知道依靠这种行为来启动 C 任务而不阻塞 Go 程序是否安全。

运行时间无法抢占真正的忙循环。一个没有调度点的 CPU 密集循环必须在它自己的线程中,其他 goroutine 才能 运行。函数调用和通道发送或接收操作都会产生。网络IO是异步调度的,文件IO有自己的线程。

通常设置 GOMAXPROCS > 1 就足够了,但是由于您有 3 个这样的循环,并且只有 2 个线程,调度程序仍然被阻塞。如果您有一个有效的 CPU 密集循环,这使得调度 goroutine 变得困难,您可以定期调用 runtime.GoSched() 以屈服于调度程序。在现实世界中,这通常唯一重要的地方是出现空循环的编程错误。

所有 cgo 调用都发生在 go 运行time 之外的它们自己的线程中,因此这些循环对主循环没有影响,除了浪费 CPU。