美文网首页
【Go 精选】从 GM 到 GMP 模型

【Go 精选】从 GM 到 GMP 模型

作者: 熊本极客 | 来源:发表于2021-09-29 22:01 被阅读0次

    本文简单介绍了 GM 和 GMP 模型,其中分析了 GMP 针对 GM 存在问题的优化点。

    1 GM 模型

    GM 模型中的 G 全称为 Goroutine 协程M 全称为 Machine 内核级线程

    全局队列存放协程 G,同时 M 内核级线程会从全局队列中获取 G。
    GM 模型的缺点:① 全局队列的锁竞争,当 M 从全局队列中添加或者获取 G 的时候,都需要获取队列锁,导致激烈的锁竞争。② M 转移 G 消耗较多资源,当 M1 在执行 G1 的时候, M1 创建了 G2,为了继续执行 G1,需要把 G2 保存到全局队列中,而 M2 刚好获取 G2 来执行。因为 M1 原本就保存了 G2 的信息,所以 G2 最好是在 M1 上执行。

    GM 模型.png

    2 GMP 模型

    GMP 模型中的 G 全称为 Goroutine 协程M 全称为 Machine 内核级线程P 全称为 Processor 本地队列。对比 GM 模型, GMP 模型新增了本地队列 P。

    M1 新增的 G 会优先被保存在 M1 所绑定的本地队列 P1 中,其中 P1 最多存放 256 个 G。如果本地队列 P1 满了,会把新增的 G 存放在全局队列中(加锁操作)。最终 M1 会从本地队列 P1 获取 G 进行调度(无锁操作)。

    GMP 模型.png

    问题1:M 如何从 P 中获取 G
    M 从 P 中获取 G 的顺序:本地队列 P 》全局队列 P 》偷别人的 P 队列,具体流程如下。
    ① M0 首先从本地队列 P 中获取 G(无锁操作)
    ② 如果本地队列 P 为空,则从全局队列 P 获取G(加锁操作)
    ③ 如果全局队列 P 为空,再去另一个本地队列 P 采用 work-stealing 算法偷取一半数量的 G。

    问题2:当 M0 在执行 G1 的时候被阻塞了,如何继续执行

    P 的数量是由环境变量 $GOMAXPROCS 或者由 runtime 的 GOMAXPROCS() 方法,其创建时机是系统运行的时候;M 的数量默认等于内核线程数,其创建时机是由内核管理,即当没有足够的 M 关联 P /原来的 M 被阻塞,内核会创建新 M 去关联 P。

    如下图所示,当 M0 在执行 G1 的时候阻塞了,M0 与 P 解绑,接着 M1 绑定 P,然后执行 G2。

    M 阻塞场景.png

    问题3:G 从创建、保存、被获取、调度和执行、阻塞的生命周期

    步骤 1:创建 G,关键字 go func() 创建 G;
    步骤 2:保存 G,创建的 G 优先保存到本地队列 P,如果 P 满了,则会保存到全局队列中;
    步骤 3:M 获取 G,M1 首先从本地队列 P1 获取 G,如果 P1 为空,则从全局队列获取 G,如果全局队列也为空,则从另一个本地队列偷取一半数量的 G;
    步骤 4:M 调度和执行 G,M1 调用 G.func() 函数执行 G,如果 M1 在执行 G 的过程被阻塞了,则本地队列 P1 与 M1 解绑。利用 hand-off 机制,新内核线程 Mx 绑定 P1,接着继续执行 P1 中其余的 G。

    G 生命周期.png

    3 GM 与 GMP 的区别

    从设计角度看,GMP 对比 GM 增加了一层本地队列 P。GMP 新增本地队列 P 后,如何优化 GM 存在的问题 —— ①全局队列锁竞争激烈;②M 转移 G 消耗较多资源

    区别 1:减少大量的全局队列锁竞争。M 绑定本地队列 P 后,直接在 P 中获取、添加和执行 G(无锁操作)。

    减少锁竞争.png

    区别 2:提高资源利用率 —— 尽量在同 1 个 M 中创建和执行 G。M 创建完 G 后,会将该 G 保存在与 M 绑定的本地队列 P 中,后续 M 会从 P 中获取和执行 G。因为 G 的信息在创建时保存到 M 中,所以在后续执行的过程中不需要转移 G 的信息,即不涉及线程上下文切换。

    区别 3:提高资源利用率 —— 获取全局队列的 G 和 work-stealing 偷其它本地队列 P 一半数量 G。当 M0 的本地队列为空,M0 先去全局队列获取 G,如果全局队列为空,再其它本地队列偷一半数量的 G。

    work-stealing 机制.png

    区别 4:提高资源利用率 —— 利用 hand-off 机制处理阻塞问题。当 M0 执行 G1 的时候被阻塞了,内核系统会调度 M1 重新绑定本地队列 P,然后继续执行 G2。

    hand-off 机制.png

    注意:如何处理本地队列 P 中的每个 G 都被阻塞的极端场景
    由于 hand-off 机制会使得 M 和 P 在成对扩增,为了限制无限扩增的极端场景,P 通过 runtime.GOMAXPROCS() 设置最大值,默认值等于 CPU 核数。

    G 阻塞的极端场景.png

    相关文章

      网友评论

          本文标题:【Go 精选】从 GM 到 GMP 模型

          本文链接:https://www.haomeiwen.com/subject/glefnltx.html