goroutine系统调度

go 刘宇帅 6年前 阅读量: 1467

关于线程

我们每运行的一个程序都会创建一个进程,每个进程都有一个初始线程,而后初始线程可以创建更多线程,每个线程互相独立地运行。线程因为其轻量和易用性在并发编程中被大量的使用。而 goroutine 就是基于线程的。线程的实现模型主要有3种:用户级线程模型、内核级线程模型和两级线程模型(或者叫做混合型线程模型)。他们之间的区别就是用户线程和系统最小调度单元内核调度实体(KSE,Kernal Scheduling Entity)的对应关系不同。

用户级线程模型

用户线程与内核线程KSE是多对一的映射模型,多个用户线程一般都是从属于单个进程,并且用户线程的调度都是用户程序的线程库完成的,不需要操作系统调度。一个进程所创建的所有线程都和同一个KSE绑定,操作系统调度只知道进程对线程无感知,这种模型实现的线程调度是用户层实现的不需要操作系统内核调度所以是轻量级的。但是这种模型最根本缺点就是并不能实现真正意义上的并发,因为单个进程的所有线程都是绑定在同一个KSE,各个用户线程交替执行,如果某一个用户线程阻塞了,那么整个进程就会阻塞。

内核级线程模型

用户线程与内核线程KSE是一对一(1:1)的映射模型,也就是每一个用户线程绑定一个真实的内核线程,那么线程调度就完全依赖于操作系统的内核调度,java语言等就是这种模式,这种模式虽然实现了真正的并行但是线程调度代价很大,严重影响系统性能。

两级线程模型

两级线程模型综合了上面两种模式优点,在此模型中用户线程和内核线程KSE是多对多的关系,与内核级线程模型不同的是两级线程模型中的进程可以和多个KSE绑定,而进程中的线程并不会和某一个KSE绑定,而会动态调整,当进程中的某一个线程阻塞了某一个KSE,那么进程中其余的线程会去绑定到其他的KSE。所以两级线程模型即不像用户线程模型完全靠用户调度也不像内核级线程模型完全依赖系统调度,而是一个中间态的模型。而 goroutine 就是调度器就是采用这种模型。

G-P-M模型

golang调度系统中有3个比较重要的概念那就是G、P、M:

  • G: goroutine,是 golang 调度系统中封装出来的调度单元。
  • P:Processor,是 golang 调度系统中封装出的“CPU“,其提供了执行环境、内存分配状态和任务队列等。P的数量决定了 golang 系统中最大并行 goroutine 的数量。
  • M 用户空间线程,同时对应一个内核线程。

    G、P、M、KSE关系

    G、P、M、KSE关系

根据图中2个概念需要说明:

  1. Global队列,队列里是待执行的G.
  2. P,每个P包含一个待执行G队列,叫做本地运行队列,队列中 G 有数量限制,最多包含256个。

当我们通过 go 关键字创建一个新的 goroutine 的时候,他会优先被放入到P的本地运行队列,如果P运行队列已满,就会放入到Global 队列中。每个M会绑定到一个 KSE ,并随机选择一个 P ,然后 M 会启动一个 OS 线程循环从绑定的P里获得 goroutine 并执行。直到P中G执行完毕,然后P会尝试从 Global 队列里获得G来执行,如果 Global 队列为空那么P会随机挑选另外一个P,从它的队列里拿来一般的G到自己的队列中执行。

M、P的关联

P的数量默认是CPU的数量或者 runtime.GOMAXPROCS() 来决定,在确定了P的数量后,go 运行时系统会创建相应数量的P。当P中本地运行队列不为空的时候,P会去绑定一个M,如果没有多余的M的话,那么系统回去创建一个M,go 系统会设置M最大数量限制,最大为10000。M和P没有绝对的关系,M会随机选择一个P绑定,当M阻塞的时候而P的本地运行队列不为空那么P就会去创建或者切换到一个新的M,所以即使P的默认数量是1也会创建出多个M。

提示

功能待开通!


暂无评论~