Golang调度器相关的两个问题请教
Golang调度器相关的两个问题请教
- Go语言的syscall函数是异步的吗?
- 调度器是否保证任何goroutine都不会出现饥饿的情况?
非常感谢您的任何回答
我指的是Go协程内所有系统调用的本质。
有一个 包 syscall。您指的是这个包中的哪些函数?
它们是同步操作,否则你无法返回系统调用的结果,但它们都会返回某个值。
不过请注意,大多数系统调用会影响所有Go协程,而不仅仅是当前协程,因为系统内核没有Go协程的概念,它只能看到调度器进程。
此外,没有任何机制可以防止单个Go协程的饥饿,但Go运行时能很好地识别死锁,当整个系统死锁时,会在运行时触发panic。
可能我之前关于Go语言系统调用异步性的表述有些模糊。 让我澄清一下:
据我正确理解,Go语言内部使用了多线程,那么为什么不采用以下方式实现:当一个goroutine需要进行系统调用时,包含该goroutine的线程(我们称之为线程A)只需将系统调用请求放入队列,然后调度线程A内的其他goroutine。之后,一个工作线程(称为线程B)可以从队列中获取请求,执行系统调用并在系统调用返回后通知线程A,这样线程A就可以唤醒该goroutine。
通过这种方式,包含goroutine的线程就是以异步方式执行系统调用的。
我想知道的是:实际的实现方式究竟是怎样的?
它不能以这种方式工作。
Go 使用工作线程。你可以有 10000 个协程,但只会有一小部分工作线程。当一个协程执行阻塞的系统调用时,工作线程会被阻塞,直到系统调用完成。因此,在阻塞的系统调用期间,可用的工作线程会减少一个。这就是 @NobbZ 所解释的。
当协程和工作线程在系统调用上被阻塞时,其他工作线程可以运行其他协程。
我猜想系统调用是同步的,是出于效率原因。如果请求必须排队并异步处理,将会在系统调用执行中引入延迟。同步系统调用简单、高效,并且只要我们有足够的工作线程和极少的阻塞系统调用,就能很好地工作。
- Go语言的syscall函数通常是同步的,但调度器会将其转换为异步处理。当goroutine执行系统调用时,调度器会将其从当前线程分离,并允许其他goroutine在该线程上运行,从而避免阻塞。例如:
package main
import (
"fmt"
"syscall"
"time"
)
func main() {
go func() {
// 同步系统调用,但调度器会异步处理
var r syscall.Rusage
syscall.Getrusage(syscall.RUSAGE_SELF, &r)
fmt.Printf("User time: %v\n", r.Utime)
}()
// 其他goroutine可以继续执行
go func() {
for i := 0; i < 5; i++ {
fmt.Println("Other goroutine running")
time.Sleep(100 * time.Millisecond)
}
}()
time.Sleep(1 * time.Second)
}
- Go调度器通过协作式抢占和GOMAXPROCS机制减少饥饿风险,但不提供绝对保证。从Go 1.14开始,调度器实现了基于信号的异步抢占,进一步降低了长时间运行goroutine导致饥饿的可能性。例如:
package main
import (
"fmt"
"runtime"
"time"
)
func cpuIntensive() {
for i := 0; i < 1000000000; i++ {
// 模拟CPU密集型任务
_ = i * i
}
fmt.Println("CPU-intensive task completed")
}
func main() {
runtime.GOMAXPROCS(1) // 单核测试
go func() {
for i := 0; i < 5; i++ {
fmt.Println("Monitoring goroutine:", i)
time.Sleep(200 * time.Millisecond)
}
}()
go cpuIntensive()
time.Sleep(3 * time.Second)
}
在这个例子中,即使设置了单核,监控goroutine仍然有机会执行,这展示了调度器的抢占机制。

