Go语言线程与协程之间的关系之GMP模型

1. GMP模型

在这里插入图片描述

这里首先给出GMP模型的调度策略. 让大家有一个全局的认识更好

2. G (groutine)

G就是goroutine的意思, 代表了一个协程. 每次go调用的时候,都会创建一个G对象,它包括栈、指令指针以及对于调用goroutines很重要的其它信息,比如阻塞它的任何channel,其主要数据结构:

type g struct {
  stack       stack   // 描述了真实的栈内存,包括上下界
  ............. // 中间还有很多部分
  m              *m     // 当前G的绑定的m
  sched          gobuf   // goroutine切换时,用于保存g的上下文      
  param          unsafe.Pointer // 用于传递参数,睡眠时其他goroutine可以设置param,唤醒时该goroutine可以获取
  atomicstatus   uint32
  stackLock      uint32 
  goid           int64  // goroutine的ID
  waitsince      int64 // g被阻塞的大体时间
  lockedm        *m     // G被锁定只在这个m上运行
  ............. // 省略
}

看这个结构我们可以得知, G需要与M进行绑定. 当然最重要的还是sched这个结构体 存储了调度时g的上下文(context)

type gobuf struct {
	// 保存CPU的rsp寄存器的值
	sp   uintptr
	// 保存CPU的rip寄存器的值
	pc   uintptr
	// 记录这个gobuf对象属于那个g
	g    guintptr
	ctxt unsafe.Pointer
	ret  sys.Uintreg
	lr   uintptr
	bp   uintptr // for framepointer-enabled architectures
}

sp, pc 寄存器的值, 栈指针等等. 记录了上下文

3. M(machine)

M代表了一个线程 . 每次创建一个M的时候,都会有一个底层线程创建;所有的G任务,最终还是依附于M上执行,其主要数据结构:

type m struct {
    g0      *g     // 带有调度栈的goroutine

    gsignal       *g         // 处理信号的goroutine
    tls           [6]uintptr // thread-local storage
    mstartfn      func()
    curg          *g       // 当前运行的goroutine
    caughtsig     guintptr 
    p             puintptr // 关联的p和执行的go代码
    nextp         puintptr
    id            int32
    mallocing     int32 // 状态

    spinning      bool // m是否out of work
    blocked       bool // m是否被阻塞
    inwb          bool // m是否在执行写屏蔽
}

其中重点要关注的是 g0 和 curg , curg代表的当前执行的groutine协程

另一个是g0,是带有调度栈的goroutine,这是一个比较特殊的goroutine。普通的goroutine的栈是在堆上分配的可增长的栈,而g0的栈是M对应的线程的栈。所有调度相关的代码,会先切换到该goroutine的栈中再执行。也就是说线程的栈也是用的g实现,而不是使用的OS的。

4. P(processor)

执行器或者处理器. 相当于一个管理者. 每一个M必须要绑定一个P. P自身有一个局部本地队列用来保存g. 并且所有的p都共享这一个全局队列

P负责调度goroutine,维护一个本地goroutine队列,M从P上获得goroutine并执行,同时还负责部分内存的管理。

下面的p的局部实现

lock mutex

    id          int32
    status      uint32 // 状态,可以为pidle/prunning/...
    link        puintptr
    schedtick   uint32     // 每调度一次加1
    syscalltick uint32     // 每一次系统调用加1
    sysmontick  sysmontick 
    m           muintptr   // 关联的m
    mcache      *mcache
    racectx     uintptr

    goidcache    uint64 // goroutine的ID的缓存
    goidcacheend uint64

  
    runqhead uint32 // 头部
    runqtail uint32 // 尾部
    // 队列在这呢
    runq     [256]guintptr   // 可运行的goroutine的队列

    runnext guintptr // 下一个运行的g
}

5.调度顺序

在这里插入图片描述
下面说一下详细的过程

  1. go func(){}创建一个新的goroutine
  2. 这个goroutine会被挑选进入一个P的本地队列, 若是本地队列满了, 那么就进入全局的队列
  3. 这时M向P要goroutine去执行了. G依附于M上运行,每个M绑定一个P。如果P的本地队列没有G,M会从全局队列寻找G, 如果全局队列也没有G, 那么P会从其他P里面窃取G
  4. M被操作系统调度到CPU上面执行, G也就执行了, 若是在此期间,M这个线程遇到阻塞或者系统调用, 那么此时P就会与M解绑. 并在M的休眠队列里面唤醒一个新的M与之绑定, 然后再去运行G.
  5. 若是在此期间是G发生了阻塞, 那么M也会阻塞. 此时M就会与P解绑, 由M1接管P.去运行其他的G. M等待阻塞的返回, 就将G放入全局队列中了, 自己就去休眠了.
  6. 运行完之后G就会被销毁退出.

5.1 协程调度

5.1.1 主动调度

主动的让出CPU资源, 此时当前协程切换到g0, 取消G与M之间的绑定关系, 再将此G放入全局队列中, 并调用schedule函数开始新一轮的循环

5.1.2 抢占式调度

当某个协程占用CPU时间过长时, go语言调度器就会强制中断执行.并保存上下文等待下次调度

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gopher333

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值