golang深度学习-协程与GMP模型

本文为博客 VIP 文章,开通 VIP 后可阅读全文

开通 VIP

操作系统进程管理

在现今流行的操作系统中,线程是进程的组成部分。一个进程中可以存在多个线程,这些线程并发执行并共享进程的内存(例如全局变量)等资源。而进程之间相对独立,不同进程具有不同的内存地址空间、代表程序运行的机器码、进程状态、操作系统资源描述符等。线程是CPU执行的最小单元,而进程是程序运行的实例。

虽然多核处理器可以保证并行计算,但是实际中程序的数量以及实际运行的线程数量会比CPU核心数多得多。因此,为了平衡每个线程能够被CPU处理的时间并最大化利用CPU资源,操作系统需要在适当的时间通过定时器中断(Timer Interrupt)、I/O设备中断、系统调用时执行上下文切换(Context Switch)。

当发生线程上下文切换时,需要从操作系统用户态转移到内核态,记录上一个线程的重要寄存器值(例如栈寄存器SP)、进程状态等信息,这些信息存储在操作系统线程控制块(Thread Control Block)中。当切换到下一个要执行的线程时,需要加载重要的CPU寄存器值,并从内核态转移到操作系统用户态。如果线程在上下文切换时属于不同的进程,那么需要更新额外的状态信息及内存地址空间,同时将新的页表(Page Tables)导入内存。

进程之间的上下文切换最大的问题在于内存地址空间的切换导致的缓存失效(例如:CPU中用于缓存虚拟地址与物理地址之间映射的TLB表、缓存行),所以不同进程的切换要显著慢于同一进程中线程的切换。

在这里插入图片描述

协程

在Go语言中,协程被认为是轻量级的线程。和线程不同的是,操作系统内核感知不到协程的存在,协程的管理依赖Go语言运行时自身提供的调度器。同时,Go语言中的协程是从属于某一个线程的。

  1. 调度方式
    协程是用户态的。协程的管理依赖Go语言运行时的调度器。同时,Go语言中的协程是从属于某一个线程的,协程与线程的对应关系为M:N,即多对多。Go语言调度器可以将多个协程调度到一个线程中,一个协程也可能切换到多个线程中执行。
  2. 上下文切换速度
    协程的速度要快于线程,其原因在于协程切换不用经过操作系统用户态与内核态的切换,并且Go语言中的协程切换只需要保留极少的状态和寄存器变量值(SP/BP/PC),而线程切换会保留额外的寄存器变量值(例如浮点寄存器)。上下文切换的速度受到诸多因素的影响,如:线程切换的速度大约为1~2微秒,Go语言中协程切换的速度比它快数倍,为0.2微秒左右。
  3. 调度策略(主动、被动、抢占式调度)
    线程的调度在大部分时间是抢占式的,操作系统调度器为了均衡每个线程的执行周期,会定时发出中断信号强制执行线程上下文切换(也叫作时间片)。
    而Go语言中的协程在一般情况下是协作式调度的,当一个协程处理完自己的任务后,可以主动将执行权限让渡给其他协程。这意味着协程可以更好地在规定时间内完成自己的工作,而不会轻易被抢占。当一个协程运行了过长时间时,Go语言调度器才会强制抢占其执行。
  4. 协程栈
    线程的栈大小一般是在创建时指定的,为了避免出现栈溢出(Stack Overflow),默认的栈会相对较大(例如2MB),这意味着每创建1000个线程就需要消耗2GB的虚拟内存,大大限制了线程创建的数量。
    而Go语言中的协程栈默认为2KB,在实践中,经常会看到成千上万的协程存在。同时,线程的栈在运行时不能更改,但是Go语言中的协程栈在Go运行时的帮助下会动态检测栈的大小,并动态地进行扩容,可以通过runtime/debug中的SetMaxStack函数设置最大值(默认最大1G)。因此,在实践中,可以将协程看作轻量的资源。
  • 并发(concurrency):在一个时间段内,可以执行多个任务。(需要借助上下文切换)
  • 并行(parallelism):在任意一个时间,可以执行多个任务。(多核处理器)

主协程与子协程

  • 主协程(main Goroutine):main函数是一个特殊的协程,当主协程退出时,程序直接退出。
  • 子协程(child Goroutine):使用 go 关键字创建的协程属于子协程。子协程之间没有父子关系。

GMP模型

Go进程中的众多协程其实依托于线程,借助操作系统将线程调度到CPU执行,从而最终执行协程。

  • G:代表的是Go语言中的协程(Goroutine)。由启动时环境变量$GOMAXPROCS或者是由runtime的方法GOMAXPROCS()决定。
  • M:代表的是实际的线程。go程序启动时,会设置M的最大数量,默认10000。可以通过runtime/debug中的SetMaxThreads函数,设置M的最大数量。当一个M阻塞了,会创建新的M。
  • P:代表的是Go逻辑处理器(Process)。
  • 全局队列(Global Queue):存放等待运行的G。
  • P的本地队列:同全局队列类似,存放的也是等待运行的G,存的数量有限,不超过256个。新建G时,G优先加入到P的本地队列,如果队列满了,则会把本地队列中一半的G移动到全局队列。

在这里插入图片描述
在任一时刻,一个P可能在其本地包含多个G,同时,一个P在任一时刻只能绑定一个M。但是一个G并不是固定绑定同一个P的,有很多情况(如:P销毁、任务窃取)会导致一个P中的G转移到其他的P中。同样的,一个P只能对应一个M,但是具体对应的是哪一个M也是不固定的。一个M可能在某些时候转移到其他的P中执行。

GMP模型与GM模型

从GMP的模型中可以发现,不引入P(Process)也是可以完成协程的调度。实际上Golang的早期模型就是使用的GM模型完成协程的调度,那么为什么引入了P呢?

我们先尝试推理下不引入P的前提下,即GM模型是如何工作的。

对于G(Goroutine)来说实际上是没有任何影响的,其次对于M(Machine)来说其实也没有太大的影响,因为M运行的是G。但是M的可执行的G是来自P的,那没有P,就需要从全局队列去获取G执行。因为有过个M可能有多个同时运行,那M在执行每一个G的时候,都需要去全局队列加锁获取。另外,当M正在执行的G阻塞时候,M无法换另外一个G去执行,因此只能整个M也陷入阻塞,与此同时一个新的M将会被创建用于执行新的G。由此可推断,当多个G均存在一些阻塞操作的时候,就可能会创建很多个M去执行G,那么多个线程竞争CPU资源的情况就会大大增加。

我们在来看看引入P之后,GMP又是如何提效的。

首先G和M的作用还是没有变化。但是M的任务获取就有很大的变化了。M的可执行G全部来自P的本地队列,那就意味着,M不在需要每次都从全局队列中加锁获取G,可以很快的从P的本地队列快速获取到G执行。另外,当M中执行的G陷入阻塞操作的时候,还是会新建一个M来继续执行G。但是陷入阻塞的M与它原来的P将会被解绑,从而这个P可以与新的M绑定继续执行。当陷入阻塞的M完成资源获取执行完后,由于它没有新的G可以获取到,因为P已经解绑,那他就会一直空闲在那里,之后就会被销毁。因此多个M去竞争CPU的情况就会大大降低。

当然引入P之后,还是有其他一些优势,比如P充当了一个中间层的角色,而中间层的作用往往都是伴有一个解耦的角色。因此P还可以负责管理一些资源,比如Goroutine的内存分配、网络轮询等。比如:当G执行阻塞操作时,P可以将其分离,让M继续执行其他G,而P则去寻找另一个可运行的G,避免阻塞整个线程,这个过程都是P来完成的。

总的来说,引入P的好处有几点:

  1. 减少全局锁竞争,通过本地队列分散调度压力
  2. 控制并发执行的G数量,P的数量通常等于CPU核心数,限制同时运行的线程数,避免过多线程切换的开销
  3. 实现工作窃取,平衡不同P之间的负载
  4. 管理资源,如网络轮询器、计时器等,与P绑定,提高处理效率

GMP生命周期及状态转换

  • G 协程
    在这里插入图片描述

    GO调度模型GMP GO的调度模型: 可以抽象出三个实体:M(machine)、P(processor)、G(goroutine)。 G:GO运行时对goroutine的描述,G中存放并发执行的代码入口地址、上下文、运行环境(关联的P和M)、运行栈等执行相关的信息。G的新建、休眠、恢复、停止都受到Go运行时的管理。 GO运行时的监控线程会监控G的调度,G不会长久地阻塞系统线程,运行时的调度器会自动切换到其他G上运行。G新建或恢复时会添加到运行队列,等待M取出并运行。 M:OS内核线程,是操作系... 阅读详情

    相关推荐

    Golang 协程/线程/进程 区别以及 GMP 详解

    Ggoroutine,即Go协程,每个go关键字都会创建一个协程。Mthread内核级线程,所有的G都要放在M上才能运行。Pprocessor处理器,调度G到M上,其维护了一个队列,存储了所有需要它来调度的G。Goroutine调度器P和OS调度器是通过M结合起来的,每个M都代表了1个内核线程,OS调度器负责把内核线程分配到CPU的核上执行,

    JohnYang's CSDN Home 1394

    go语言GMP模式介绍以及协程案例展示

    Go语言的调度模型被称为GMP,这是一个高效且复杂的调度系统,用于在可用的物理线程上调度goroutines(Go的轻量级线程)。GMP模型由三个主要组件构成:Goroutine、M(机器)和P(处理器)。:管道在内部实现了必要的同步机制,因此在多个goroutines访问时是安全的。关闭管道后,不能再向管道发送数据,但仍可以接收管道中已存在的数据。:无缓冲管道在发送或接收时会阻塞,直到另一端准备好。尝试向已关闭的管道发送数据会引发panic。循环从管道接收数据,直到管道被关闭。

    qq_49195366的博客 2967

    Ubuntu 22.04下PBC库Golang环境集成全攻略

    双线性配对是现代密码学中的核心数学工具,它允许在椭圆曲线群上构建特殊的映射关系,为多种高级密码学协议提供了理论基础。其技术原理基于椭圆曲线上的Weil配对或Tate配对,能够实现群元素间的双线性运算。这一特性带来了显著的技术价值,使得基于身份的加密、短签名、聚合签名等协议成为可能,极大地提升了密码学方案的表达能力和效率。在应用场景上,双线性配对技术被广泛应用于区块链、隐私计算和分布式系统安全等领域。本文聚焦于在Ubuntu 22.04系统中,如何解决PBC(Pairing-Based Cryptograph

    weixin_33971977的博客 350

    go GMP调度原理

    gmp

    weixin_41565755的博客 5774

    Golang深度学习

    在2009年,Google公司发布了一种新的编程语言,名为Go(或称为Golang),旨在提高编程效率、简化并发编程,并提供强大的标准库支持。Go语言的设计者们希望通过Go语言能够解决软件开发中的一些长期存在的问题,比如并发编程的复杂性、垃圾回收机制的效率以及跨平台的兼容性等。自从2009年发布以来,Go语言已经经历了多个版本的迭代和改进,持续吸引着全球开发者的兴趣和使用。其简洁性、高效性以及强大的并发处理能力使其成为构建高性能网络服务的首选语言之一。

    G970620的博客 1674

    golang深度学习-性能分析

    Go语言原生支持在运行时保留重要的特征指标和状态,有许多工具可以分析甚至可视化程序运行的状态和过程。

    G970620的博客 1074

    GMP 调度器深度学习笔记

    A: Go 使用分段栈(Segmented Stack,Go 1.2-)或连续栈(Contiguous Stack,Go 1.3+)。4. **某些 P 的 runqueue 特别大**:负载不均,可能某些 Goroutine 创建了过多子任务。g0 *g // 用于执行调度代码的特殊 g(栈更大)3. **runqueue 持续积累**:全局队列有大量待处理的 G,可能有性能瓶颈。gFree *g // 已终止的 G 的缓存列表。

    qq_37314512的博客 363

    Go Goroutine 底层源码深度学习

    基于 Go 1.27 源码,面向后端大厂面试,深入解析 goroutine 底层实现原理。

    追求幸福,探索未知的博客 378

    最全Go爬虫学习笔记(二)_go语言爬虫学习,2024年最新大厂面试流程慢

    这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。最近我才对这些路线做了一下新的更新,知识体系更全面了。包含了Python入门、爬虫、数据分析和web开发的学习视频,总共100多个,虽然没有那么全面,但是对于入门来说是没问题的,学完这些之后,你可以按照我上面的学习路线去网上找其他的知识资源进行进阶。

    2401_84572577的博客 691

    go深入学习

    目录基础**数据结构****内置函数**结构体接口反射**错误异常**其他注意点进阶编译slicemapstringchanselect并发控制调度模型内存管理内存对齐源码httpsql性能调优内存泄漏 基础 数据结构 1、值类型: bool int(32 or 64), int8, int16, int32, int64 uint(32 or 64), uint8(byte), uint16, uint32, uint64 float32, float64 stri

    wxd1234567890的博客 1009

    Go后端 vs Go AI应用开发重点关注什么?怎么学?怎么面试?

    Go后端岗适合喜欢“建系统、稳架构”的人,是技术生态的“压舱石”;AI应用岗适合对AI感兴趣、擅长“落地转化”的人,是风口上的“弄潮儿”。俩方向不冲突——后端能力是AI应用的基础,AI知识能让后端工程师更有竞争力。不管选哪条路,“动手实战+持续学习”都是唯一的通关密码。后端工程师多盯CNCF动态,AI应用工程师多关注Go和AI框架的新集成。把技术学扎实,用项目攒经验,你肯定能在Go技术浪潮里站稳脚跟,一路升级打怪!如果你对这篇文章的内容感兴趣,欢迎链接我:wangzhongyang1993。

    王中阳讲编程 853

    AI如何在真实生物实验中落地:从代码到GMP合规的实践路径

    人工智能在生物技术中的应用,正从概念验证迈向真实产线部署。理解AI湿实验结合的本质,需回归计算生物学基础——即如何将非结构化实验操作(如移液精度、pH波动、离心参数)转化为算法可处理的结构化语义。其技术价值在于 bridging the gap between digital models and physical biology,通过实验元数据建模、条件感知特征工程干湿闭环执行,显著提升药物研发效率精准医疗可靠性。典型应用场景包括高通量筛选数据实时解读、慢病毒滴度快速预测、CRISPR脱靶风险动态评

    weixin_30723433的博客 342

    golang大杀器GMP模型

    golang大杀器GMP模型学习

    fengxiandada的博客 6087

    Go~并发之GMP原理调度

    文章目录前言routine线程的关系N:1关系1:1关系M:N关系Go 语言协程 goroutineGo中的调度器被废弃的 goroutine 调度器Goroutine 调度器的 GMP 模型的设计思想GMP 模型P 和 M 的个数问题P 和 M 何时会被创建调度器的设计策略go func () 调度流程调度器的生命周期代码示例分析可视化 GMP 编程Go 调度器调度场景过程全解析 前言 看这篇文章,请确保你已经熟悉进程、线程、协程等相关概念。并熟悉了操作系统常用的线程调度方式。并对并发和并行有一定

    Shangxingya的博客 1883

    GO GMP

    GO GMP 组成、特征、调度场景

    qq_43157273的博客 1505

    Golang GMP调度模型详解

    文章目录前言1. Goroutine调度器的基本概念2. Goruntine调度策略2.1 work stealing机制2.2 hand off 机制 前言      线程数过多,意味着操作系统会不断地切换线程,频繁的上下文切换就成了性能瓶颈。      Golang的调度模型GMP模型,它提供一种机制,可以在线程中自己实现调度,上下文切换更轻量,从而达到了线程数少,而并发数并不少的效果。而线程中调度的就

    爱意时刻减淡,玫瑰不再浪漫 4548

    GO高阶: 调度器 GMP 原理调度全分析

    目录 Golang协程调度器原理及GMP设计思想? 一、Golang“调度器”的由来? (1) 单进程时代不需要调度器 (2)多进程/线程时代有了调度器需求 (3)协程来提高CPU利用率 (4)Go语言协程goroutine (5)被废弃的goroutine调度器 二、Goroutine调度器的GMP模型的设计思想 (1)GMP模型 (2)调度器的设计策略 (3) go func() 调度流程 (4)调度器的生命周期 (5)可视化GMP编程 三、Go调度器调度场景过程全解

    琦彦 1184

    深入Golang调度器之GMP模型

    随着服务器硬件迭代升级,配置也越来越高。为充分利用服务器资源,并发编程也变的越来越重要。在开始之前,需要了解一下并发(concurrency)和并行(parallesim)的区别。并发:逻辑上具有处理多个同时性任务的能力。并行:物理上同一时刻执行多个并发任务。通常所说的并发编程,也就是说它允许多个任务同时执行,但实际上并不一定在同一时刻被执行。在单核处理器上,通过多线程共享CPU时间片串行执行(并发非并行)。而并行则依赖于多核处理器等物理资源,让多个任务可以实现并行执行(并发且并行)。

    金戈鐡馬 681
    上一篇: golang深度学习-基础篇
    下一篇: golang深度学习-并发控制
    老狼伙计
    博客等级 码龄10年 268粉丝 · 22原创
    评论 1
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

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

    余额充值