• 技术文章 >后端开发 >Golang

    深入浅析Go语言中要有GMP调度模型的原因

    青灯夜游青灯夜游2023-04-14 15:26:34转载38

    Go为什么要有GMP调度模型?下面本篇文章给大家介绍一下Go语言中要有GMP调度模型的原因,希望对大家有所帮助!

    GMP调度模型是Go的精髓所在,它合理地解决了多线程并发调度协程的效率问题。

    GMP是什么

    首先得清楚,GMP各代指什么东西。

    线程M各自持有一个处理器P,当要获取协程时优先从P中获取,于是GMP模型图解如下:

    image.png

    大致流程是,线程M从P的队列中获取协程,如果获取不到,就会从全局队列中去竞争锁来获取。

    处理器P

    协程G和线程M结构在前几篇已经讲解了,这里解析一下处理器P。

    作用

    处理器P储存着一批协程,使得线程M可以无锁的从中获取协程,而无需与其他线程去竞争全局队列中的协程,从而提高调度协程效率。

    源码解析

    p结构体源码在src\runtime\runtime2.go中,这里展示部分重要字段。

    type p struct {
       ...
       m           muintptr   // back-link to associated m (nil if idle)
       // Queue of runnable goroutines. Accessed without lock.
       runqhead uint32
       runqtail uint32
       runq     [256]guintptr
       runnext guintptr
       ...
    }

    image.png

    线程M与处理器P是如何协作的?

    src\runtime\proc.go中,有一个schedule方法,这是线程运行的第一个函数。这函数中,线程需要获取到可运行的协程,代码如下:

    func schedule() {    
        ...
        // 寻找一个可运行的协程
        gp, inheritTime, tryWakeP := findRunnable() 
        ...
    }
    func findRunnable() (gp *g, inheritTime, tryWakeP bool) {
        // 从本地队列中获取协程
        if gp, inheritTime := runqget(pp); gp != nil {
           return gp, inheritTime, false
        }
    
        // 本地队列拿不到则从全局队列中获取协程
        if sched.runqsize != 0 {
           lock(&sched.lock)
           gp := globrunqget(pp, 0)
           unlock(&sched.lock)
           if gp != nil {
              return gp, false, false
           }
        }
    }

    从本地队列中获取协程

    func runqget(pp *p) (gp *g, inheritTime bool) {
       next := pp.runnext // 队列中下一个可运行的协程
       if next != 0 && pp.runnext.cas(next, 0) {
          return next.ptr(), true
       }
       ...
    }

    那如果本地队列和全局队列中都没有协程了怎么办呢,难道就让线程这么闲着?

    这时候处理器P就会任务窃取,从其他线程的本地队列中窃取一些任务,美其名曰分担其他线程的压力,还提高了自己线程的利用率。

    源码在src\runtime\proc.go\stealWork中,感兴趣可以看看。

    新建的协程该分配到哪?

    新建的协程该分配到本地还是全局队列呢,得分情况:

    实际流程为:

    1. 随机寻找P
    2. 将新协程放入P的runnext中,意味着下一个就运行该协程,插队了
    3. 若P的协程满了,则放入全局队列

    源码在src\runtime\proc.go\newproc函数中。

    // Create a new g running fn.
    // Put it on the queue of g's waiting to run.
    // The compiler turns a go statement into a call to this.
    func newproc(fn *funcval) {
       gp := getg()
       pc := getcallerpc()
       systemstack(func() {
          newg := newproc1(fn, gp, pc) // 创建新协程
    
          pp := getg().m.p.ptr()
          runqput(pp, newg, true) // 寻找本地队列放入
    
          if mainStarted {
             wakep()
          }
       })
    }

    结语

    本篇初步介绍了GMP调度模型,具体介绍了处理器P以及线程M获取协程的方式。

    处理器P解决了多线程互斥获取协程的问题,提高调度协程的效率,但是不管协程在本地还是全局队列,目前看来还只是顺序执行,那Go是如何实现协程异步并发执行的呢?咱下一篇继续分析(虽然没什么人看...)。

    推荐学习:Golang教程

    以上就是深入浅析Go语言中要有GMP调度模型的原因的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:后端 Go
    上一篇:如何在Go中进行Post请求 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • 如何在Go语言中隐藏窗口• 创建Golang项目时如何设置网站• 探讨Golang中的扩展方法• 聊聊如何在Golang中设置代理• 聊聊Golang中的转码系统及其相关技术• 如何在Go语言中使用HTTPS协议进行请求
    1/1

    PHP中文网