Go运行时如何检查goroutine是否被阻塞?

Dmi*_*kov 5 concurrency go goroutine

Go 文档说:

当协程阻塞时,例如通过调用阻塞系统调用,运行时会自动将同一操作系统线程上的其他协程移动到不同的可运行线程,这样它们就不会被阻塞

但是运行时如何检测 goroutine 被阻塞呢?

例如,如果我将在 go-routine 之一中运行计算,它会被评估为阻塞操作吗?

package main

import (
    "fmt"
    "runtime"
)

func f(from string, score int) {
    for i := 0; i < score; i++ {
            for z := 0; z < score; z++ {
        }
    }

    fmt.Println(from, " Done")
}   

func main() {
runtime.GOMAXPROCS(1)
f("direct", 300000)
go f("foo", 200000)
go f("bar", 20000)
go f("baz", 2000)

 go func(msg string) {
        fmt.Println(msg)
    }("going without loop")

       var input string
    fmt.Scanln(&input)
    fmt.Println("done")
}
Run Code Online (Sandbox Code Playgroud)

我得到的结果是:baz,boo bar。但为什么?Go 知道这foo是阻塞吗?

typ*_*ris 4

这张票与以下问题相关:

\n\n

https://github.com/golang/go/issues/11462

\n\n

您可以执行的每个阻塞调用都将由运行时提供服务。因此运行时知道是否发生可能阻塞的事情。

\n\n

例如:

\n\n
    \n
  1. 如果您调用Lock()a,sync.Mutex运行时将处理该问题并检查是否会阻塞并采取相应的行动。

  2. \n
  3. 如果您在运行时调用Run()or Output()(或类似),exec.Cmd则运行时会注意到并假设该调用将被阻止。它无法知道您正在运行的程序是否会阻塞,因此它必须假设最坏的情况。

  4. \n
\n\n

据我所知,有两种主要机制,即 goroutine 如何阻塞,上面的示例是每种机制的示例。

\n\n
    \n
  1. 是运行时在没有“外部”帮助的情况下向您提供的内容的示例。

  2. \n
  3. 是涉及系统调用的示例。例如,Linux 上的 Golang 不使用 gnu libc,而是通过调用 os.golang 直接实现所需的系统调用。所有这些调用都通过包进行syscall(据我所知),这里运行时有一个钩子来通知发生了什么。

  4. \n
\n\n

当然,图片可能有点混乱,golang 需要来自操作系统的互斥体来实现跨操作系统线程同步,即使对于 1 也是如此,然后它在某种程度上也有点像示例 2。

\n\n

关于问题中的代码:不。如果编译器没有优化循环,Go 不明白 f 可能会花费很多时间。在如此紧密的循环中,go 调度程序无法“停止”该 goroutine 并将另一个 goroutine 设置为运行,因为循环中没有抢占点。因此,如果你有多个这样的 goroutine 在没有抢占点的情况下执行紧密循环,它们可能会耗尽你的 cpu,并且所有其他 goroutine 都必须等待,直到至少一个紧密循环完成。\n但只在其中调用不同的函数循环改变了这一情况,函数调用是一个抢占点。

\n\n

用户1432751在评论中提问:

\n\n
\n

当阻塞操作发生时,CPU 寄存器会发生什么?\n 当前线程被 goroutine 阻塞,Go 调度程序创建新的\n 系统线程并将所有其他线程迁移到那里?\xe2\x80\x93

\n
\n\n

go 调度不是抢占式的(至少这是我上次检查的状态),而是在某些抢占点进行调度。例如系统调用、在通道上发送和等待以及如果我没记错的话函数调用。因此,在这些点上,当调度程序决定要做什么时,内部函数被调用,并且与 goroutine 中执行的代码相关的 cpu 寄存器已经在堆栈中。

\n\n

是的,如果系统调用完成,因此存在操作系统线程被阻塞的危险,执行系统调用的 goroutine 会获得自己的操作系统线程,这甚至不计入 GOMAXPROCS。

\n