V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
Jianzs
V2EX  ›  Go 编程语言

Go 是协作式调度,还是抢占式调度?

  •  
  •   Jianzs ·
    pluto-lang · 2023-03-28 11:15:51 +08:00 · 4521 次点击
    这是一个创建于 640 天前的主题,其中的信息可能已经有所发展或是发生改变。

    首先,介绍我对 GoLang 调度过程的认识,如果有误,请指出。

    GoLang 的调度模型是 GMP 调度模型,调度的计算实体是 Goroutine ,调度的资源载体是 Machine (线程)。用户创建的 Goroutine 会被提交至 Processor ,Processor 会根据当前空闲与否决定将 Goroutine 是否调度到一个线程执行,当 Processor 上当前线程执行的 Goroutine 结束或挂起时,则会调度新的 Goroutine 至某线程。

    接下来,介绍我的疑惑:

    • 一方面,当协程退出或 IO 阻塞时主动让出占用的计算资源,供其他 Goroutine 使用,这是典型的协作式调度。
    • 另一方面,当一个协程长时间占用计算资源,runtime 会将其强制中断,进而让其他 Goroutine 都能有机会得到执行,这应该是抢占式调度。

    从以上两方面,感觉 Go 既有协作式?又有抢占式?但是为什么大家都说 Go 是协作式呢?

    站内大佬颇多,还请解惑~

    21 条回复    2023-03-28 21:05:17 +08:00
    emSaVya
        1
    emSaVya  
       2023-03-28 11:26:24 +08:00
    印象里 go 1.14 有一个 update 。转向 preemptive
    centralpark
        2
    centralpark  
       2023-03-28 11:30:08 +08:00   ❤️ 6
    你感觉是对的呀,相对传统线程,goroutine 有协作式调度;相对传统 coroutine ,goroutine 也可以抢占式调度。

    这不就跟某知名运动员一样,在中国是中国人,在美国是美国人。

    大家都说他是协作式,那是因为传统的并发方式是多进程和多线程,相对他们而言的。
    AugOmin
        3
    AugOmin  
       2023-03-28 11:38:54 +08:00
    都有
    zeonll
        4
    zeonll  
       2023-03-28 11:56:35 +08:00
    协作式调度是在后面的版本才加进来的吧。(加入了协作调度功能,被营销号吹成 转成协作调度)
    之前的版本都是抢占,所以两种方式现在都有。
    leonshaw
        5
    leonshaw  
       2023-03-28 13:10:25 +08:00 via Android
    对 goroutine 来说应该是抢占式,IO 调用并没有主动让出的意思,而是被 runtime 插入的调度点。Gosched()才算协作。
    Kisesy
        6
    Kisesy  
       2023-03-28 13:25:11 +08:00
    因为 go 会不断更新啊,你参考的信息又不一定是最新的
    rockyliang
        7
    rockyliang  
       2023-03-28 13:25:20 +08:00   ❤️ 1
    @zeonll 你是说反了吗,应该是老版本是协作式调度,后面才加入的抢占式调度
    DarkCat123
        8
    DarkCat123  
       2023-03-28 13:33:27 +08:00
    > Goroutines are now asynchronously preemptible. As a result, loops without function calls no longer potentially deadlock the scheduler or significantly delay garbage collection. ( https://go.dev/doc/go1.14)


    @leonshaw Goroutine 现在会在编译的时候被编译器插入类似 Gosched() (实际是用 semaphore )
    zoharSoul
        9
    zoharSoul  
       2023-03-28 13:40:06 +08:00
    大家都说 go 的是抢占式吧 相对于别的协程
    DiffView
        10
    DiffView  
       2023-03-28 13:45:45 +08:00   ❤️ 3
    你对于 GoLang 的调度过程和 GMP 调度模型的理解基本上是正确的。

    关于你的疑惑,GoLang 的调度模型的确是协作式调度模型。虽然在某些情况下,GoLang 会使用抢占式调度来强制中断长时间占用计算资源的 Goroutine ,但这并不代表整个调度模型是抢占式的。

    在 GoLang 的调度模型中,每个 Goroutine 都有一个 Goroutine 栈和调度器关联。当 Goroutine 遇到阻塞或等待 IO 时,调度器会将该 Goroutine 标记为不可运行,让出 CPU 资源给其他可运行的 Goroutine 。当 IO 操作完成或其他等待条件满足时,该 Goroutine 再次标记为可运行,调度器会在适当的时候重新调度该 Goroutine 。

    同时,GoLang 的调度器会根据一定的规则,例如时间片轮转算法和休眠唤醒机制等,在运行中的 Goroutine 之间进行切换,保证每个 Goroutine 都能得到执行机会。这种协作式调度模型能够更好地利用 CPU 资源,避免了不必要的上下文切换开销,也避免了抢占式调度可能带来的问题,例如死锁和资源争用等。

    综上所述,尽管 GoLang 的调度模型在某些情况下会使用抢占式调度,但整个调度模型仍然是协作式调度模型。因此,大家通常会将 GoLang 的调度模型称为协作式调度模型。
    8355
        11
    8355  
       2023-03-28 13:52:54 +08:00   ❤️ 1
    我帮你问了 chatgpt4

    你对 GoLang 调度过程的认识基本正确。的确,Go 语言的调度模型是 GMP 模型,涉及到 Goroutine ( G )、Processor ( P )和 Machine ( M )三个主要概念。

    关于你的疑惑,Go 语言的调度机制主要是协作式调度。然而,从 Go 1.14 版本开始,Go 在协作式调度的基础上加入了一些抢占式调度的特性。这主要体现在 goroutine 抢占方面,通过在执行过程中插入抢占点(例如函数调用)来实现。这样,当一个 Goroutine 长时间占用计算资源时,runtime 可以强制中断它,让其他 Goroutine 有机会得到执行。

    尽管 Go 语言引入了这些抢占式调度特性,但它仍然主要是协作式调度。这是因为 Goroutine 在许多情况下主动让出计算资源(例如,当遇到 IO 阻塞时),这是协作式调度的核心特点。而抢占式调度特性只是在特定情况下作为补充,以确保在某些场景下调度的公平性和响应性。

    因此,大家通常认为 Go 语言主要采用协作式调度,而抢占式调度仅在特定情况下作为辅助。这也是为什么大家说 Go 是协作式调度的原因。
    8355
        12
    8355  
       2023-03-28 13:53:48 +08:00
    @DiffView #10 这个应该也是 gpt 的回答 哈哈
    DiffView
        13
    DiffView  
       2023-03-28 13:54:43 +08:00
    @8355 笑死,因为我也想知道答案,但是网上根本没有答案
    GopherDaily
        14
    GopherDaily  
       2023-03-28 13:58:38 +08:00
    协作式,但是为了一些场景一直不让出控制权,后续加入了一定的抢占的场景
    centralpark
        15
    centralpark  
       2023-03-28 14:17:24 +08:00
    @DiffView @8355 你俩是真不怕封号……
    DiffView
        16
    DiffView  
       2023-03-28 14:21:07 +08:00
    这里我比较有兴趣,后面也查了下资料

    从 go 1.14 开始,go 调度器是非合作抢占的。每个 goroutine 在一定的时间片后被抢占。在 go 1.19.1 中是 10ms 。
    代码在这里: https://github.com/golang/go/blob/go1.19.1/src/runtime/proc.go#L5279-L5281
    zmcity
        17
    zmcity  
       2023-03-28 14:36:27 +08:00
    混合式。

    但 golang 是以能方便的写协作调度出名的。
    8355
        18
    8355  
       2023-03-28 14:54:08 +08:00
    @centralpark #15 帮 op 问一问只回一个 不是机器人自动回啊。 😭
    leonshaw
        19
    leonshaw  
       2023-03-28 15:51:15 +08:00   ❤️ 1
    @DarkCat123 “协作式”调度时,指的是谁在协作?应该是指用户协程,而不是 runtime. 当一个 goroutine 陷入 IO 调用或者 prologue ,它本身是没有让出计算资源的主观意愿的,这一点可以对比显式的 Gosched() 调用和其它语言的 await. 同样也可以类比系统线程,在系统调用返回前,计算资源可能被释放并切换到其它线程,这应该也不算协作,因为线程并没有想要释放资源,是操作系统的决策。
    jdz
        20
    jdz  
       2023-03-28 20:03:17 +08:00 via Android
    协作式效率高,协程抢占式没意义
    bruce0
        21
    bruce0  
       2023-03-28 21:05:17 +08:00
    @jdz 我记得抢占式是有意义的, 协作式是在函数调用时,检查是否让出执行, 就会出现一种情况

    ```
    func main() {
    runtime.GOMAXPROCS(1)
    go func() {
    for {
    }
    }()
    time.Sleep(time.Millisecond)
    println("OK")
    }
    ```
    这段代码在 go1.14 之前不会打印 OK 的 就是因为协程中没有函数调用,也就没有协作式的检查点, 协程就不会退出
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2150 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 00:54 · PVG 08:54 · LAX 16:54 · JFK 19:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.