Go 的并发机制(1)

Go 的并发机制

4.1 原理探究

Go 其实时在操作系统提供的内核线程之上,搭建了一个特有的两级线程模型。

goroutine 的含义:不要用共享内存的方式来通信,作为替代,应该以通信作为手段来共享内存。

  • Go 使用 channel 在多个 goroutine 之间传递数据,并且会保证整个过程的并发安全性。

4.1.1 线程实现模型

核心元素:
  • M:一个 M 代表一个内核线程,或者说“工作线程”
  • P:一个 P 代表执行一个 Go 代码片段所必需的资源(“上下文环境”)
  • G:goroutine 的缩写
M、P、G之间的关系
  1. 一个 G 的执行需要 P 和 M 的支持。一个 M 和 一个 P 关联之后,就形成了一个有效的 G 运行环境(内核线程+上下文环境)
  2. 每个 P 都会包含一个可运行的 G 的队列,该队列中的 G 会依次传递给本地 P 关联的 M,获得运行时机。

从宏观来看,他们之间的联系可以如下图所示:
在这里插入图片描述
如果将焦点扩大一点,从内核+用户空间层面看:
在这里插入图片描述

  • 可以看到,M 与 KSE 总是一对一的关系,一个 M 能且仅能代表一个内核线程。
  • M 与 P 之间也总是一对一的,而 P 与 G 之间则是一对多的关系(队列)
  • M 与 G 之间也会建立关联,因为一个 G 终归会由一个 M 来负责运行,两者通过 P 来牵线
    注:上图只作为一般性的示意,这三者之间的关系在调度过程中会多变

接下来,继续

1. M

一般来说,创建一个 M,都是因为没有足够的 M 来关联 P 并运行其中可运行的 G。不过,在运行时系统执行系统监控或者垃圾回收等任务的时候,也会导致 M 的创建。

M的部分结构如下:
在这里插入图片描述

  • g0:是一个特殊的 goroutine,是 Go 运行时系统在启动之初创建的,用于执行一些运行时任务
  • mstartfn:表示 M 的起始函数,就是go 携带的那个
  • curg:存放当前 M 正在运行的那个 G 的指针
  • p:指向与当前 M 相关联的那个 P
  • nextp:用于暂存与当前 M 有潜在关联的 P,赋值即预联(M 重启时,会将 nextp 赋值为 p)
  • spinning:表示这个 M 是否正在寻找可运行的 G(寻找过程中 M 处于自旋状态)
  • lockedg:表示当前 M 锁定的那个 G(如果有的话,一旦锁定,这个 M 就只能运行这个 G)
1.1 M 的创建与停止
创建与执行
  1. M 在创建之初,会被加入全局的 M 列表中。此时,它的起始函数和预联的 P 也会被设置。
  2. 运行时系统,会为这个 M 专门创建一个新的内核线程,并与之相关联。
  3. M创建之后,Go 运行时系统会先对其进行初始化:栈空间、信号处理等。初始化完成之后,M 的起始函数将会执行。(如果起始函数是系统监控任务的话,M 会一直执行它,不会进行后面的流程)
  4. 起始函数执行完成之后,M 会与预联的 P 完成关联
停止

系统停止 M 的时候,会把它放入调度器的空闲 M 列表(运行时系统会优先从这个列表获取)

一般来说,可以认为 Go 本身对于线程数量(10000)没有限制。因为操作系统内核对进程的虚拟内存的布局控制以及大小限制,一般难以共存如此量级的线程。

2. P
P 的最大数量设置
  • 调用函数 runtime.GOMAXPROCS 设置数量
  • 最好在Go 程序的 main 函数的最前面调用 runtime.GOMAXPROCS(因为这个函数调用的执行会暂时让所有的 P 都脱离运行状态,并试图阻止任何用户级别的 G 的运行)
  • M 的数量一般会比 P 多
    原因:当 M 因为系统调用而阻塞的时候,运行时系统会把该 M 和与之关联的 P 分离开来。如果这时候 P 的可运行 G 队列中还有未被运行的 G,那么运行时系统会找到一个空闲 M(或者创建一个新的 M),并与该 P 关联以满足这些 G 的运行需要。
  • P 的最大数量确定之后,运行时系统会重整全局的 P 列表,调度放到某个 P 的可运行 G 队列中
  • 当一个 P 的可运行 G 列表为空的时候,会被放入空闲 P 列表(比如说重整全局的 P 列表的时候,P 被清空可运行 G 队列之后,才会被放入空闲 P 列表)
P 的状态转换
  • Pidle:当前 P 未与任何 M 存在关联
  • Prunning:当前 P 正在与某个 M 关联
  • Psyscall:当前 P 中的运行的那个 G 正在进行系统调用
  • Pgcstop:表明运行时系统需要停止调度(运行时系统在开始垃圾回收的某些步骤前,就会试图把全局 P 列表中的所有 P 都置于此状态)
  • Pdead:当前 P 已经不会再被使用(只能被销毁)
    在这里插入图片描述
3. G
G 的状态
  • Gidle:当前 G 刚被新分配,但还未初始化
  • Grunnable:当前 G 正在可运行队列中等待运行
  • Grunning:当前 G 正在运行
  • Gsyscall:当前 G 正在执行某个系统调用
  • Gwaiting:当前 G 正在阻塞
  • Gdead:当前 G 正在闲置(可以重新初始化并使用)
  • Gcopystack:当前 G 的栈正被移动,移动的原因可能是栈的扩展或收缩
    在GC 扫描时,会发生一些组合状态,如:Gscanrunning 等
    在这里插入图片描述
4. 核心元素的容器

在这里插入图片描述
在这里插入图片描述
本节,我们一直是将实现和操纵 Go 的线程实现模型的内部程序笼统地称为“运行时系统”。实际上,它可以更明确地称为”调度器“,我们下一节再详细讲述。

  • 16
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Jiangw557

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

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

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

打赏作者

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

抵扣说明:

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

余额充值