go 协程模型

311 阅读3分钟

在了解go协程模型前,先简要的介绍下线程的实现模型。

线程实现模型

线程的实现模型主要包括三种:

  • 用户级线程模型
  • 内核级线程模型
  • 两级线程模型

三种模型的主要区别是内核调度实体(KSE)与用户级线程的对应关系上。

KSE:操作系统内核的最小调度单元

用户级线程模型

1.png

KSE与用户线程是1:N的关系,即内核不区分进程内部的具体线程,而是将整个进程视为一个调度实体。

这种模型下:

  • 由用户的调度器选择一个线程关联到KSE上,KSE只是单纯的执行者
  • 非真正的并发,因为只会用到一个KSE(单个进程下)
  • 进程内部的线程切换可以做的很轻,无需在内核态和用户态之间相互切换

内核级线程模型

2.png

KSE与用户线程是1:1的关系,每个线程都会由一个KSE拿去执行。

这种模型下:

  • KSE与用户线程一一对应(当然超出KSE的线程需要等待)
  • 真正的并发
  • 需要频繁的在内核态和用户态之间进行切换

两级线程模型

3.png

KSE与用户线程是M:N的关系,每个线程可以独立的绑定到自己想绑定的KSE上,没有约束。

这种模型下:

  • KSE与用户线程没有约束,每个KSE可以绑定多个用户线程

go 协程模型

go 是两级线程模型:

  • go 调度器:实现 goroutine 到 kse的调度
  • 内核调度器:实现 kse 到 cpu 的调度

GMP模型

go 是通过 G、M、P 模型实现二级线程模型的

G

  • goroutine,一个可以独立运行的协程,go 调度器的核心就是在传送 G。
  • 拥有栈、程序计数器、状态、工作函数,相当于待执行的 task
  • 有多种状态的转化,可参考线程在操作系统中的状态机

M

  • 操作系统的计算单元,与一个内核线程(KSE)绑定,代表真正的计算单元,由内核调度器调度和管理
  • 拥有执行的状态信息(栈的起止位置,是否空闲等),以及与P的绑定关系,相当于真正的worker

P

  • 逻辑处理器,是G与M中间缓冲带,是实现二级线程模型的关键
  • 拥有任务队列G(局部、全局),执行上下文等,相当于dispatcher,决定是否将队列里的G交给当前关联的M执行
  • 调度器会创建 runtime.GOMAXPROCS 个P,默认等于 CPU 数

4.png

从图中我们可以看出:

  • M与P一一绑定,存在空闲M的场景(or M空闲的场景)
  • 每个P都有一个局部的G队列,同时全局会有一个G队列
  • 每个P都有一个正在运行的G

调度

  1. 每隔一段时间会从全局G队列中,取出可运行的G
  2. 从本地G队列中,取出可运行的G
  3. 阻塞等待可用的G

执行流程

  1. 将当前G绑定到M上

  2. 将当前G置为运行中

  3. 调用gogo,执行用户逻辑

  4. 结束时,调用goexit0

    1. 将G置为结束
    2. 清理G,解绑G与M
    3. 再次进入调度,从而实现了调度循环

内核态,用户态, 进程, 线程, 协程,  多路IO - 天使的魅影 - 博客园

The Go scheduler - Morsing's blog

关于Go并发编程,你不得不知的“左膀右臂”——并发与通道!_高可用架构的博客-CSDN博客

Go 语言调度(二): goroutine 调度器 - 简书