Go并发模型 M,P,G

CSP 并发模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯管道 channel 进行通信的并发模型,它不同于传统的并发实体通过共享内存来通信。CSP 的基本原则是 “通过通信来共享内存,而不要通过共享内存来通信”。

Go 的 CSP 并发模型,是通过 goroutine 和 channel 来实现的。它不推荐使用共享内存方式在并行体之间来传递数据,而推荐使用 channel 在多个 goroutine 之间传递数据,来保证整个过程的并发安全性。不过,作为可选方法,Go 依然提供了一些传统的同步方法(比如互斥量、条件变量等),用于共享内存。

在操作系统提供的内核线程之上,Go 创建了一个两级线程模型。协程 goroutine 是 Go 特有的轻量级的应用程序线程。

Go 的线程实现模型,有三个核心的元素 M、P、G,它们共同支撑起了这个线程模型的框架。

  • M:machine 的缩写。一个 M 代表一个内核线程。
  • P:processor 的缩写。一个 P 代表 M 执行 Go 代码片段所需的资源,即上下文环境。
  • G:goroutine 的缩写。一个 G 代表一段需要并发执行的 Go 代码片段的封装。

一个 G 的执行需要 P 和 M 的支持。一个 M 与 P 关联之后(内核线程+上下文环境),就形成了一个有效的运行环境。

每个 P 都会包含一个可运行的 G 的队列(runq),队列中的 G 会依次传递给与 P 关联的 M,从而获得运行机会。

我们通常把运行当前 G 的 M 称为 “当前M”。

M、P 和 G 之间的关系如下图所示:

Go并发模型

M、P 和 G 与内核调度实体(KSE)之间的关系如下图所示:

Go并发模型

M 与 KSE 之间总是一对一的关系,一个 M 代表一个内核线程。Go 的运行时系统(runtime)用 M 代表一个内核调度实体。

一个 M 在其生命周期内,仅会与一个 KSE 产生关联,它们是一对一的关系。

M 与 P、P 与 G 之间的关联都是变化的,它们之间的关系会在实际调度的过程中改变。

M 与 G 之间也会建立关联,因为一个 G 终归会由一个 M 来负责运行,它们之间通过 P 来进行关联。

Go 的运行时系统(runtime)会对这三类实体的实例进行管理和调度。

G 即 goroutine,又称为轻量级的用户线程,或者协程。它是一个可以并发执行的实体,底层使用 coroutine 实现并发,coroutine 是一种运行在用户态的用户线程。Go 底层选择使 ...