Go 运行时如何检查 goroutine 是否被阻塞?
How Go runtime checks if goroutine is blocked?
Go 文档说:
When a coroutine blocks, such as by calling a blocking system call, the run-time automatically moves other coroutines on the same operating system thread to a different, runnable thread so they won't be blocked
但是运行time如何检测到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")
}
我得到的结果是:baz, boo bar。但为什么? Go 理解 foo
是阻塞的吗?
这种情况下的阻塞通常发生在 goroutine 要求运行时完成工作时,通常是通过系统调用。例如,在套接字上监听——goroutine 告诉系统的 kqueue/epoll 部分在数据到达时唤醒它,此时它很明显被阻塞了。
Goroutines 在等待通道上的消息时也会被阻塞 - 这里 goroutine 明确告诉运行时它不会做任何其他事情直到它收到消息,所以运行时非常清楚 goroutine被封锁。
大多数 "do this and give me a result" 或 "give me this when it's ready" 形式的操作都是可验证的阻塞操作,并且在准备就绪之前进入休眠状态。
这张票与问题相关:
https://github.com/golang/go/issues/11462
您可以执行的每个阻塞调用都将由运行时服务。所以运行时知道是否发生了可能阻塞的事情。
例如:
如果您在 sync.Mutex
上调用 Lock()
,运行时将处理它并检查是否会阻塞并采取相应行动。
如果您在 exec.Cmd
上调用 Run()
或 Output()
(或类似的),运行时会注意到并假定此调用将被阻止。它不知道你的程序 运行 是否会阻塞,所以它必须假设最坏的情况。
据我所知,有两种主要机制,goroutine 如何阻塞,上面的示例是每种机制的示例。
是运行时在没有 "external" 帮助的情况下为您提供的示例。
是涉及系统调用的示例。例如,linux 上的 Golang 不使用 gnu libc,而是通过调用 os 直接实现它需要的系统调用。所有 those 调用都通过包 syscall
(据我所知),这里运行时有一个钩子来通知发生的事情。
当然,图片可能有点模糊,golang 需要来自 os 的互斥锁,用于 cross os 线程同步,即使是 1。和那么它在某种程度上也是示例 2.
关于问题中的代码:不。如果编译器没有优化循环,Go 不明白 f 可能会花费很多时间。并且在如此紧凑的循环中,go scheduler 无法 "stop" goroutine 并将另一个 goroutine 设置为 运行,因为循环中没有抢占点。所以如果你有多个这样的 goroutines 做紧密的循环
如果没有抢占点,它们可能会吃掉你的 cpus 并且所有其他 goroutines 必须等待,直到至少一个紧密循环完成。
但只有在该循环中调用不同的函数才能改变画面,
作为一个函数调用是一个抢占点。
user1432751 在评论中提问:
what happens with CPU registers when blocking operation is happen?
Current thread is blocking by goroutines, Go scheduler create new
system thread and migrate all other threads there? –
go schedule 不是抢占式的(至少那是我上次检查时的状态),而是在某些抢占点进行调度。例如系统调用、发送和等待通道,如果我没记错的话函数调用。因此在ose点调用内部函数并且cpu与在goroutine中执行的代码相关的寄存器已经在堆栈上,当调度程序决定做什么时。
是的,如果系统调用已完成,因此存在阻塞 os 线程的危险,执行系统调用的 goroutine 将获得自己的 os 线程,这甚至不算数转到 GOMAXPROCS。
几个月前我遇到了同样的问题,我解决了这个问题,添加了一个应该在更短时间内更新的频道,就像看门狗一样:
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
}
}
希望对你有用
Go 文档说:
When a coroutine blocks, such as by calling a blocking system call, the run-time automatically moves other coroutines on the same operating system thread to a different, runnable thread so they won't be blocked
但是运行time如何检测到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")
}
我得到的结果是:baz, boo bar。但为什么? Go 理解 foo
是阻塞的吗?
这种情况下的阻塞通常发生在 goroutine 要求运行时完成工作时,通常是通过系统调用。例如,在套接字上监听——goroutine 告诉系统的 kqueue/epoll 部分在数据到达时唤醒它,此时它很明显被阻塞了。
Goroutines 在等待通道上的消息时也会被阻塞 - 这里 goroutine 明确告诉运行时它不会做任何其他事情直到它收到消息,所以运行时非常清楚 goroutine被封锁。
大多数 "do this and give me a result" 或 "give me this when it's ready" 形式的操作都是可验证的阻塞操作,并且在准备就绪之前进入休眠状态。
这张票与问题相关:
https://github.com/golang/go/issues/11462
您可以执行的每个阻塞调用都将由运行时服务。所以运行时知道是否发生了可能阻塞的事情。
例如:
如果您在
sync.Mutex
上调用Lock()
,运行时将处理它并检查是否会阻塞并采取相应行动。如果您在
exec.Cmd
上调用Run()
或Output()
(或类似的),运行时会注意到并假定此调用将被阻止。它不知道你的程序 运行 是否会阻塞,所以它必须假设最坏的情况。
据我所知,有两种主要机制,goroutine 如何阻塞,上面的示例是每种机制的示例。
是运行时在没有 "external" 帮助的情况下为您提供的示例。
是涉及系统调用的示例。例如,linux 上的 Golang 不使用 gnu libc,而是通过调用 os 直接实现它需要的系统调用。所有 those 调用都通过包
syscall
(据我所知),这里运行时有一个钩子来通知发生的事情。
当然,图片可能有点模糊,golang 需要来自 os 的互斥锁,用于 cross os 线程同步,即使是 1。和那么它在某种程度上也是示例 2.
关于问题中的代码:不。如果编译器没有优化循环,Go 不明白 f 可能会花费很多时间。并且在如此紧凑的循环中,go scheduler 无法 "stop" goroutine 并将另一个 goroutine 设置为 运行,因为循环中没有抢占点。所以如果你有多个这样的 goroutines 做紧密的循环 如果没有抢占点,它们可能会吃掉你的 cpus 并且所有其他 goroutines 必须等待,直到至少一个紧密循环完成。 但只有在该循环中调用不同的函数才能改变画面, 作为一个函数调用是一个抢占点。
user1432751 在评论中提问:
what happens with CPU registers when blocking operation is happen? Current thread is blocking by goroutines, Go scheduler create new system thread and migrate all other threads there? –
go schedule 不是抢占式的(至少那是我上次检查时的状态),而是在某些抢占点进行调度。例如系统调用、发送和等待通道,如果我没记错的话函数调用。因此在ose点调用内部函数并且cpu与在goroutine中执行的代码相关的寄存器已经在堆栈上,当调度程序决定做什么时。
是的,如果系统调用已完成,因此存在阻塞 os 线程的危险,执行系统调用的 goroutine 将获得自己的 os 线程,这甚至不算数转到 GOMAXPROCS。
几个月前我遇到了同样的问题,我解决了这个问题,添加了一个应该在更短时间内更新的频道,就像看门狗一样:
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
}
}
希望对你有用