前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go调度系列--GMP是什么?(一)

Go调度系列--GMP是什么?(一)

原创
作者头像
小许code
发布2023-03-14 12:00:30
1.4K0
发布2023-03-14 12:00:30
举报
文章被收录于专栏:小许code小许code

前言

做为Go开发者基本上对GMP已经很熟悉,这是Go的核心内容,三个核心部分共同配合下让Go 调度器得以高效运转。结合之前我们对编译和启动流程的总结,现在就更容易从结构和汇编调用的实际函数来进行结合理解,我们先来看Go调度器的组成部分GMP各部分的结构和用处。

注:文中GMP的底层数据结构都在src/runtime/runtime2.go中,每个结构体的字段数比较多,只截取了一部分进行了说明。

G(Goroutine)

G 取自Goroutine的首字母,一个G代表一个Goroutine,Goroutine是一个与其他Goroutines并行运行在同一地址空间的go函数或方法。

它是一个数据结构,这个结构体里面有栈信息,上下文等。它占用了更小的内存空间(2KB内存),同时也降低了上下文切换的开销,Goroutine数量理论上只受内存大小限制。

在使用go 关键字创建一个goroutine时候,会调用runtime.newproc来创建一个Goroutine,在我们将Go程序的启动流程的时候就清楚,它的汇编代码如下,调用的是src/runtime/proc.go的newproc()函数,newproc 方法会切换到调用 newproc1 函数进行 G 的创建。newproc1 方法很长,里面主要是获取 G ,然后对获取到的 G 做一些初始化的工作。

代码语言:javascript
复制
// asm_amd64.s
CALL	runtime·newproc(SB)

//proc.go
func newproc(siz int32, fn *funcval) {
 argp := add(unsafe.Pointer(&fn), sys.PtrSize)
 gp := getg()  //返回g实际的结构体指针
 pc := getcallerpc()
 systemstack(func() {
  newg := newproc1(fn, argp, siz, gp, pc)

  _p_ := getg().m.p.ptr()
  runqput(_p_, newg, true)

  if mainStarted {
   wakep()
  }
 })
}

我们看g结构体的源码部分,结构体包含了g使用的栈,可能的panic和defer链、以及运行现场等信息。

代码语言:javascript
复制
type g struct {
   stack       stack   // g 使用的栈
   stackguard0 uintptr // offset known to liblink
   stackguard1 uintptr // offset known to liblink

   _panic       *_panic // panic链表
   _defer       *_defer // defer延迟函数链表
   m            *m      // 当前绑定的m
   sched        gobuf
   // 其他运行现场信息
   ...
}

M(Machine)

M是一个线程或称为Machine(取首字母),G 需要调度到 M 上才能运行,M 是真正的执行者,调度器最多可以创建 10000 个线程。

最多只会有 GOMAXPROCS 个活跃线程能够正常运行,因为M需要和P绑定才能运行G,而P的个数取决于设置的GOMAXPROCS,一个M阻塞了就会创建新的M。

M的数量和P不一定匹配,可以设置很多M,M和P绑定后才可运行,多余的M处于休眠状态。

m结构体部分源码如下:

代码语言:javascript
复制
// 底层数据结构
type m struct {
 g0      *g     // goroutine with scheduling stack
 divmod  uint32 
 ...
    curg     *g         // M当前绑定的结构体G
 ...
}

这里的g0和curg是两个不同的goroutine。g0 是一个运行时中比较特殊的 Goroutine,它会深度参与运行时的调度过程,包括 Goroutine 的创建、大内存分配和 CGO 函数的执行,curg 是在当前线程上运行的用户 Goroutine。

关于g0的特殊性总结如下:

  1. g0 所有调用栈的goroutine,这是一个比较特殊的goroutine。
  2. 普通的goroutine栈是在Heap分配的可增长的stack,而g0的stack是M对应的线程栈。
  3. 所有调度相关代码,会先切换到该g0 goroutine的栈再执行。

P(Processor)

P理解为处理器,它是M和G的中间层,负责调度M上的等待队列,通过处理器 P 的调度,每一个M都能够执行多个 Goroutine。

P的个数由runtime.GOMAXPROCS进行指定,默认是被设置为可用的CPU核数,比如你有8核处理器,那么P的数量就是默认8个。

P需要和M进行绑定才能执行G,当 M 被阻塞时,整个 P 会被传递给其他 M ,或者说整个 P 被接管。

代码语言:javascript
复制
type p struct {
 id          int32
 status      uint32 // one of pidle/prunning/...
 link        puintptr
 schedtick   uint32     // incremented on every scheduler call
 syscalltick uint32     // incremented on every system call
 sysmontick  sysmontick // last tick observed by sysmon
 m           muintptr   // back-link to associated m (nil if idle)
 ...
}

总结

简单介绍了 Go 语言调度器中GMP的底层数据结构,包括线程 M、处理器 P 和 Goroutine G。三者关系:G需要绑定在M上才能运行,M需要绑定P才能运行,M 会从与它绑定的 P 的本地队列获取可运行的 G,还会从其他 P 偷 G。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • G(Goroutine)
  • M(Machine)
  • P(Processor)
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档