goroutine 可以在什么时候产生?

gre*_*van 1 go green-threads

我试图更好地了解 Go 程序中 goroutines 是如何调度的,尤其是在它们可以让步给其他 goroutines 的时候。我们知道 goroutine 会产生会阻塞它的 syscals,但显然这不是全部。

这个问题引起了一些类似的担忧,最受好评的答案是 goroutine 也可能打开函数调用,因为这样做会调用调度程序来检查堆栈是否需要增长,但它明确表示

如果你没有任何函数调用,只是一些数学运算,那么是的,goroutine 会锁定线程,直到它退出或遇到可能让其他人执行的东西。

我写了一个简单的程序来检查和证明:

package main

import "fmt"

var output [30]string      // 3 times, 10 iterations each.
var oi = 0

func main() {
    runtime.GOMAXPROCS(1)   // Or set it through env var GOMAXPROCS.
    chanFinished1 := make(chan bool)
    chanFinished2 := make(chan bool)

    go loop("Goroutine 1", chanFinished1)
    go loop("Goroutine 2", chanFinished2)
    loop("Main", nil)

    <- chanFinished1
    <- chanFinished2

    for _, l := range output {
        fmt.Println(l)
    }
}

func loop(name string, finished chan bool) {
    for i := 0; i < 1000000000; i++ {
        if i % 100000000 == 0 {
            output[oi] = name
            oi++
        }
    }

    if finished != nil {
        finished <- true
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:我知道在数组中放置一个值并在oi没有同步的情况下递增是不太正确的,但我想让代码保持简单并且没有可能导致切换的东西。毕竟,可能发生的最糟糕的事情是在不推进索引(覆盖)的情况下放置一个值,这没什么大不了的。

此答案不同,我避免使用作为 goroutine 启动append()loop()函数中的任何函数调用(包括内置函数),而且我还GOMAXPROCS=1根据文档明确设置了哪个

限制可以同时执行用户级 Go 代码的操作系统线程数。

然而,在输出我仍然看到消息Main/ Goroutine 1/Goroutine 2交错,这意味着下列操作之一:

  • goroutine 的执行中断,goroutine 在某些时刻放弃控制;
  • GOMAXPROCS 不像文档中所说的那样工作,启动更多的操作系统线程来调度 goroutines。

无论是 答案不完整,要么自 2016 年以来发生了一些变化(我在 Go 1.13.5 和 1.15.2 上进行了测试)。

如果问题得到了回答,我很抱歉,但我既没有找到解释为什么这个特定的例子产生控制,也没有找到关于 goroutines 一般产生控制的点(阻塞系统调用除外)。

注意:这个问题纯粹是理论上的,我现在不打算解决任何实际任务,但总的来说,我假设知道 goroutine 可以在哪些地方可以让步,哪些地方不能让我们避免冗余使用同步原语。

tor*_*rek 5

Go 1.14 版引入了异步抢占:

Goroutines 现在是异步可抢占的。因此,没有函数调用的循环不再可能使调度程序死锁或显着延迟垃圾收集。这是所有平台都支持除windows/armdarwin/armjs/wasm,和plan9/*

通道是否为 goroutine 调度发送抢占点中的回答,Go 的抢占点可能会从一个版本到下一个版本发生变化。异步抢占只是在几乎所有地方都增加了可能的抢占点。

您对output数组的写入不同步,并且您的oi索引不是原子的,这意味着我们无法确定输出数组会发生什么。当然,使用互斥体为其添加原子性会引入协作调度点。虽然这些不是协作调度切换的来源(必须根据您的输出发生),但它们确实干扰了我们对程序的理解。

所述output阵列保持的字符串,并使用字符串可以调用垃圾收集系统,该系统可以使用锁和原因调度切换。所以这是在 Go-1.14 之前的实现中调度切换的最可能原因。