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



Go文档说:

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

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

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

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")
}

我得到了结果:巴兹,布巴。但为什么呢?Go知道foo正在阻塞吗?

此票证与以下问题相关:

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

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

例如:

  1. 如果在sync.Mutex上调用Lock(),运行时将处理它,并检查它是否会阻塞并相应地采取行动。

  2. 如果在exec.Cmd上调用Run()Output()(或类似的),则运行时会注意到并假定此调用将被阻止。它不知道你正在运行的程序是否会被阻止,所以它不得不假设最坏的情况。

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

  1. 是运行时在没有"外部"帮助的情况下为您提供的东西的示例。

  2. 是一个涉及系统调用的例子。例如,linux上的Golang不使用gnu-libc,而是通过调用操作系统直接实现所需的系统调用。所有这些调用都通过包syscall(据我所知),运行时只有一个钩子来通知发生了什么。

当然,情况可能有点混乱,golang需要一个来自操作系统的互斥体来进行跨操作系统线程同步,即使是1。然后它在某种程度上也是示例2的一部分。

关于问题中的代码:不。Go不明白,如果编译器没有优化循环,f可能会花费很多时间。在这样一个紧密的循环中,go调度器无法"停止"goroutine并将另一个goroutine设置为正在运行,因为循环中没有抢占点。所以,如果你有很多这样的goroutine在做紧密的循环如果没有抢占点,它们可能会吃掉你的cpu,所有其他goroutine都必须等待,直到至少完成一个紧循环。但只有在那个循环中调用不同的函数才能改变画面,因为函数调用是一个抢占点。

user1432751在评论中问道:

发生阻塞操作时,CPU寄存器会发生什么?当前线程被goroutines阻塞,Go调度程序创建新线程系统线程并迁移所有其他线程?-

go计划不是抢占式的(至少上次检查时是这样),而是在某些抢占点进行计划。例如syscalls,在通道上发送和等待,如果我记得正确的话,函数调用。因此,在这些点上,调用一个内部函数,当调度器决定要做什么时,与goroutine中执行的代码相关的cpu寄存器已经在堆栈上了

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

这种情况下的阻塞通常发生在goroutine要求运行时执行工作时,通常是通过系统调用。例如,监听套接字-goroutine告诉系统的kqueue/epoll部分在数据到达时将其唤醒,此时它显然会被阻塞。

当在通道上等待消息时,goroutine也会被阻止——这里goroutine明确告诉运行时,在收到消息之前,它不会做任何其他事情,所以运行时非常清楚地知道goroutine被阻止了。

大多数形式为"做这个并给我一个结果"或"准备好后给我这个"的操作都是可验证的阻塞操作,并且在准备好之前一直处于休眠状态。

几个月前我遇到了同样的问题,我解决了这个问题,添加了一个应该在更短时间内更新的通道,就像看门狗:

out := make(chan bool)      
go func() {             
for {
// Do some stuff here
time.Sleep(10 * time.Millisecond) // leaves CPU free for 10ms
out <- true             
}
}()     
// main loop
for { 
select {            
case <-out: // Jump here when out channel is filled             
case <-time.After(10 * time.Second): // Jump here if there is no channel activity for 10s
}       
}

我希望它对你有用。

最新更新