Golang并发(2)

Golang 并发(二)

参考 链接:https://juejin.cn/post/7075723579303657485

https://blog.meowrain.cn/api/i/2025/01/27/NlR4Nj1737983192379735795.avif

GMP 模型

https://blog.meowrain.cn/api/i/2025/01/27/sfaO9L1737982781574067337.avif

https://blog.meowrain.cn/api/i/2025/01/27/atHbyx1737983230550634832.avif

image-20250127210754190

image-20250127210803489

image-20250127210818402

Goroutine 是 Go 语言中的协程,实现了轻量级并发,和传统的线程相比,Goroutine 有以下特点:

  1. 轻量级
  2. 高效调度:Go 运行时在用户态进行调度,避免了频繁的上下文切换带来的开销,使得调度更加高效

GMP 模型基本概念

G: Goroutine

M: Machine

P: Processer

Goroutine 是 Go 语言中的协程,代表一个独立的执行单元,Goroutine 比线程更加轻量级,启动一个 Goroutine 的开销非常小,Goroutine 的调度由 Go 运行时在用户态进行。

Machine: 代表操作系统的线程,实际执行 Go 代码,一个 M 可以执行多个 Goroutine,但是同一时间只能执行一个 Goroutine。M 和操作系统的线程直接对应,Go 运行时通过 M 来利用多核 CPU 的并行计算能力。

Processer: P 代表执行上下文,管理者可运行的 Goroutine 队列,并且负责和 M 进行绑定,P 的数量决定了可以并行执行 Goroutine 的数量。Go 运行时会根据系统的 CPU 核数设置 P 的数量。

在 GMP 模式中,线程是物理生产者,调度器会将 goroutine 分派给一个线程。

image-20250127211428146

说明:

  1. 全局队列:要执行 goroutines 的队列
  2. P 本地队列: 和全局队列一样,它包含要执行的 goroutine,但是这个队列的最大容量是 256.当本地队列满的时候,会将 goroutie 的一般发送到全局队列,最后 G 创建的 goroutine 将在同一个队列中排队,确保本地关联
  3. P 列表: 一旦确定 GOMAXPROCS 的值,所有 P 的列表都将会被创建
  4. M 正在运行的线程: 从 P 的本地队列获取任务,如果该队列为空,M 将从全局队列获取 goroutines 到 P 的本地队列,或者从其他的 P 的队列获取 goroutines。G 在 M 中运行,当任务完成的时候,它将继续拉一个新的 G

MP 创建周期

  • P: 在确定 P 的数量后,(runtime)运行时将创建 P。
  • M: 如果没有足够的 M 来执行 P 的任务,它将被创建。(所有 M 都被阻止,将创建新的 M 来运行 P 的任务)

调度器机制

调度过程

  1. 首先创建一个 G 对象,然后 G 被保存在 P 的本地队列或者全局队列(global queue),这时 P 会唤醒一个 M。
  2. P 按照它的执行顺序继续执行任务。M 寻找一个空闲的 P,如果找得到,将 G 移动到它自己。
  3. 然后 M 执行一个调度循环:调用 G 对象->执行->清理线程->继续寻找 Goroutine。

调度策略

  • Work Stealing 机制:当本线程没有可执行的 G 时,优先从全局 G 队列中获取一批 G。如果全局队列中没有,则尝试从其他 P 的 G 队列中偷取 G。
  • Hand Off 机制:当本线程因 G 进行系统调用等阻塞时,线程会释放绑定的 P,把 P 转移给其他空闲的 M 执行。

Concurrency(并发):至少有更多的 GoMaxProc goroutines 同时运行。但是,如果 GOMACPROCS<CPU 内核,它还设置了并发限制。

Preemptive(抢占):协同程序必须放弃 cpu 时间。但是在 golang,一个 goroutine 每次最多可以运行 10 毫秒,以避免其他 goroutine 饥饿。

Global Goroutines Queue(全局 goroutine 队列):当工作窃取失败时,M 可以从全局队列中拉出 goroutines。

goroutine 实现

go func() 触发流程

image-20250127212512737

1
go func(){}`创建一个新的`goroutine

G 保存在 P 的本地队列,如果本地队列满了,保存在全局队列

G 在 M 上运行,每个 M 绑定一个 P。如果 P 的本地队列没有 G,M 会从其他 P 的本地队列,挥着 G 的全局队列,窃取 G

当 M 阻塞时,会将 M 从 P 解除。把 G 运行在其他空闲的 M 或者创建新的 M

当 M 恢复时,会尝试获得一个空闲的 P。如果没有 P 空闲,M 会休眠,G 会放到全局队列

生命周期

7b37b936-8a90-497b-81ad-bd8ffe838b9b

流程说明

  1. runtime 创建 M0,G0
  2. 调度器初始化:初始化 M0,栈,垃圾回收,创建初始的长度为GOMAXPROCS的 P 列表
  3. runtime.main创建代码的main.main,创建主 goroutine,然后放到 P 的本地队列
  4. 启动 M0, M0 绑定 P
  5. 根据 goroutine 的栈和调度信息,M0 设置运行环境
  6. 在 M 中运行 G
  7. G 退出,runtime.main调用defer,panic,最后调用runtime.exit

通信顺序进程模式

image-20250127212858941

image-20250127212907106

多线程共享内存模式

image-20250127213014304


相关内容

0%