Go docs说:
当协程阻塞时,例如通过调用阻塞系统调用,运行时自动将同一操作系统线程上的其他协同程序移动到另一个可运行的线程,这样它们就不会被阻塞
但运行时如何检测goroutine被阻止?
例如,如果我在其中一个例程中运行计算,它会被评估为阻塞操作吗?
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,嘘吧。但为什么? Go是否理解foo
正在阻止?
答案 0 :(得分:2)
此票证与问题相关:
https://github.com/golang/go/issues/11462
您可以执行的每个阻止调用都将由运行时提供。所以运行时知道是否发生了可能阻塞的事情。
例如:
如果你在Lock()
上调用sync.Mutex
,运行时将处理该问题,并检查是否阻止,并采取相应行动。
如果您在Run()
上调用Output()
或exec.Cmd
(或类似内容),运行时会注意到并假设此调用将阻止。它无法知道您运行的程序是否会阻塞,因此它必须假设最坏的情况。
据我所知,有两个主要机制,goroutine如何阻止,上面的例子是每个机制的例子。
是运行时为没有&#34;外部&#34;提供给你的东西的一个例子。帮助
是一个涉及系统调用的事情的例子。例如,Linux上的Golang没有使用gnu libc并通过调用os直接实现它所需的系统调用。所有这些调用都是通过包syscall
(据我所知),这里运行时只有一个钩子来通知发生了什么。
当然图片有点混乱,golang需要一个来自os的互斥体来进行跨os线程同步,即使是1.然后它也是某种方式的例子2。
关于问题中的代码:不。如果循环没有被编译器优化,那么Go不会理解f可能需要花费很多时间。在这样一个紧密的循环中,go调度程序不能停止&#34;停止&#34; goroutine并将另一个设置为running,因为循环中没有抢占点。所以,如果你有很多这样的goroutines做紧密循环 如果没有抢占点,他们可能会吃掉你的cpus而所有其他goroutine必须等待,直到完成至少一个紧密循环。 但只在该循环中调用不同的函数会改变该图像, 作为函数调用是抢占点。
user1432751在评论中提问:
阻塞操作发生时CPU寄存器会发生什么? 当前线程被goroutines阻塞,Go调度程序创建新的 系统线程并迁移所有其他线程吗? -
go计划不是先发制人(至少那是我上次检查时的状态),而是在某些抢占点进行计划。例如系统调用,发送和等待通道,如果我记得正确的函数调用。所以在那些点上调用一个内部函数,当调度程序决定做什么时,cpu寄存器与goroutine中执行的代码相关联已经在堆栈上。
是的,如果系统调用已经完成,因此存在被阻塞的os线程的危险,那么执行系统调用的goroutine会获得自己的操作系统线程,这甚至不计入GOMAXPROCS。
答案 1 :(得分:1)
在这种情况下阻塞通常发生在goroutine要求运行时工作时,通常是通过系统调用。例如,在套接字上进行侦听 - goroutine告诉系统的kqueue / epoll部分在数据到达时将其唤醒,此时它被非常明显地阻止。
在等待频道上的消息时,Goroutines也被阻止 - 这里goroutine明确地告诉运行时它在收到消息之前不会做任何其他事情,所以运行时非常清楚地知道goroutine被封锁了。
表单大多数操作&#34;执行此操作并给我一个结果&#34;或者&#34;当它准备就绪时给我这个&#34;都是可以验证的阻止操作,并在准备好之前一直睡觉。
答案 2 :(得分:0)
几个月前我遇到了同样的问题,我解决了这个问题,添加了一个应该在一定时间内更新的频道,例如看门狗:
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
}
}
我希望它对您有用。