GMP

93 阅读2分钟

线程、协程、goroutine

线程

协程

用户级线程,从属于一个内核级线程。无法并行,从属于同一线程的一批协程中只要有一个阻塞,这个线程上所运行的协程将全部阻塞

goroutine

经过Golang优化过后的特殊“协程” 与线程存在映射关系

通过Go调度器来实现动态映射

  1. 与线程存在映射关系,M:N,是多对多模型,映射关系是动态调整的
  2. 协程的创建、调度、销毁发生在内核态,对内核透明
  3. goroutine在多对多模型下可以实现并行
  4. 栈空间大小可以动态调整

对比

gmp模型

goroutine + machine + processor

g

goroutine拥有自己的运行栈、状态、以及执行的任务函数(用户通过go func来指定)。g需要绑定到p才能执行,在g的视角中,p就是他的cpu

type g struct {
    m *m  
    sched gobuf  
}

type gobyf struct {
    sp uintptr  
    pc uintptr  
    ret uintptr 
    bp uintptr // for framepointer-enabled architectures
}
  • m:指向当前g的m
  • sched.sp:保存cpu中的rsp寄存器中的值,指向函数调用栈的栈顶
  • sched.pc:保存cpu中的rip寄存器中的值,指向程序下一条指令的地址
  • sched.ret:保存系统调用的返回值
  • sched.bp:保存cpu中的rbp寄存器的值,存储函数栈帧的起始位置

m

m即machine,是golang中对线程的抽象。线程不直接执行g,而是先和p绑定,由其实现代理。

type m struct {
    g0 *g    // goroutine with scheduling stack
    tls
}
  • g0:始祖协程一类特殊的调度协程,不用于执行g之间的切换调度,与m的关系是为1:1

p

p即processor,是golang中的调度器。因为processor的存在,goroutine和线程之间可以实现灵活映射。

type p struct {
    // ...
    runqhead uint32
    runqtail uint32
    runq  [256]guintptr
    
    runnext guintptr
    // ...
}
  • runq:本地goroutine队列,最大长度为256
  • runqhead:队列头部
  • runtail:队列尾部
  • runnext:下一个可执行的goroutine

schedt

type schedt struct {
    // ...
    lock mutex
    // ...
    runq gQueue
    runsize int32
    // ...
}

sched是全局goroutine队列的封装

  • lock:操作全局队列的锁
  • runq:全局gorotine队列
  • runqsize:全局goroutine队列的容量

gmp

如果某个p发现本地队列和全局队列都没有可以用的g,则会从其他p的本地队列中调度部分g来实现负载均衡。

调度流程

g0和g的转换

四种调度类型

获取可执行的g

work-stealing