Go语言 GMP 中的 Work Stealing 机制

发布于 1周前 作者 h691938207 最后一次编辑是 5天前 来自 问答

Go语言GMP指的是 G(Goroutine)、M(Machine)、P(Processor),Go语言 GMP 中的 Work Stealing 机制

获取 P 本地队列,当从绑定 P 本地 runq 上找不到可执行的 g,尝试从全局链 表中拿,再拿不到从 netpoll 和事件池里拿,最后会从别的 P 里偷任务。P此 时去唤醒一个 M。P 继续执行其它的程序。M 寻找是否有空闲的 P,如果有则将 该 G 对象移动到它本身。接下来 M 执行一个调度循环(调用 G 对象->执行-> 清理线程→继续找新的 Goroutine 执行)

1 回复

在Go语言的并发模型中,GMP(Goroutine, M(Machine,代表执行Go代码的操作系统线程), P(Processor,代表执行Go代码的虚拟处理器))架构是其核心。其中,Work Stealing(工作窃取)机制是GMP模型用来提高Go程序并发性能的关键技术之一。这种机制允许空闲的M(机器)从其他忙碌的P(处理器)的队列中“窃取”待执行的Goroutine来执行,以此减少线程等待时间,提高资源利用率。

Work Stealing 机制简介

在GMP模型中,每个P都维护一个Goroutine队列,用于存放等待在该P上执行的Goroutine。当某个M执行完其当前P上的所有Goroutine后,它会变成空闲状态。此时,该M会尝试从其他P的队列中“窃取”Goroutine来执行,这个过程就是Work Stealing。

示例(非直接代码实现,因为Work Stealing由Go运行时自动管理)

虽然我们不能直接通过代码来控制或展示Work Stealing的具体实现,但可以通过一个简单的Go程序来观察其效果:

package main

import (
    "fmt"
    "runtime"
    "sync"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Printf("Goroutine %d starting\n", id)
    // 模拟工作负载
    runtime.Gosched() // 允许其他goroutine运行
    fmt.Printf("Goroutine %d finishing\n", id)
}

func main() {
    runtime.GOMAXPROCS(4) // 设置P的数量为4

    var wg sync.WaitGroup

    for i := 0; i < 20; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
    fmt.Println("All goroutines have finished.")
}

在这个例子中,我们创建了20个Goroutine,但只设置了4个P(通过runtime.GOMAXPROCS(4))。这意味着在任意时刻,只有4个Goroutine可以直接在P上执行,而其他的Goroutine会被放置在P的队列中等待执行,或者如果P上的Goroutine执行完毕且其他P有等待的Goroutine,则可能发生Work Stealing。

注意

  • Work Stealing是Go运行时自动管理的,不需要(也不允许)开发者直接干预。
  • runtime.Gosched() 函数用于让出当前Goroutine的执行权,让其他Goroutine有机会运行,这有助于在测试或演示中观察并发行为,但不应在实际程序中作为并发控制的手段。
  • 调整runtime.GOMAXPROCS的值可以影响P的数量,进而影响并发性能,但并非总是设置得越高越好,因为过多的P可能会导致线程切换的开销增加。
回到顶部