Golang并发(二)

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

GMP模型

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

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