【Go语言基础】调度器模型GPM与垃圾回收器GC

2024-08-23 07:04

本文主要是介绍【Go语言基础】调度器模型GPM与垃圾回收器GC,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

系列综述:
💞目的:本系列是个人整理为了Go语言学习的,整理期间苛求每个知识点,平衡理解简易度与深入程度。
🥰来源:材料主要源于Go语言趣学指南进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验自证。
🤭结语:如果有帮到你的地方,就点个赞关注一下呗,谢谢🎈🎄🌷!!!

🙌请先收藏,未完待续…


文章目录

    • 零、概述
      • 什么是Go语言
    • 一、基本语法
    • 二、Go的执行
      • 概述
      • 调度器scheduler
      • Goroutine
      • Processor
      • Machine
      • 监控
    • 三、垃圾回收
      • 概述
      • 算法原理
        • 语言比较
    • 二、编译
    • 参考博客


😊点此到文末惊喜↩︎


零、概述

什么是Go语言

  1. Go语言的特点
    • 编译型语言,执行效率高
    • 原生的并发支持,支持轻量级的携程和通信机制
    • 具有内存回收机制,支持安全自动的管理内存
  2. 第一个Go程序
    package main	// 声明当前文件所属的包
    import (		// 引入其他包供当前文件使用"fmt"		// fmt:用于格式化输入输出的包
    )
    // 注意 { 必须和func在同一行
    func main() {	// 定义一个名字为main的函数// 变量和常量var variable = 123;	// 变量的声明variable *= 2;		// 运算符简写variable++;			// go中没有前置++const k = 11;		// 常量的声明// 格式化打印fmt.Println("hello world",1*2)fmt.Printf("%-15v : %4v\n", "SpaceX", 94)// %v为占位符,前面的正负数字表示占用的位数,不足使用空格填充
    }
    // 函数定义
    func swap(x, y string) (string, string) {
    return y, x
    }
    
  3. Go程序的起点
    • main包中的main函数。当运行一个Go程序时,编译器会自动寻找main包,并执行其中的main函数。
  4. 常量和变量

一、基本语法

  1. Go函数中的多个返回值是哪几种
    • 返回值类型,表示函数的执行结果
    • 返回error类型,表示函数的执行成功与否的情况
  2. Go中的数据类型类型
    • 值类型
      • 布尔类型(bool):表示true/false的值
      • 整数类型(int)
        • 有符号整型:包括int, int8, int16, int32, int64
        • 无符号整型:包括uint, uint8, uint16, uint32, uint64`
        • 指针无符号整型uintptr:用于和底层编程交互,常用于存储指针的整形表示
      • 浮点数类型:包括单精度浮点数(float32)双精度浮点数(float64)
      • 复数类型:由实部和虚部组成,有单精度复数(complex64)双精度复数(complex128)
      • 字符串类型(string):表示一串字符。
      • 字符类型(rune):表示一个Unicode字符。
      • 数组类型(array):具有固定大小和相同类型的连续元素的集合。
      • 结构体类型(struct):表示不同类型的字段组合。
    • 引用类型
      • 切片类型(slice)
        • 定义:是一个动态数组结构,包含起始位置、长度和容量。
        • 操作:超过容量会进行扩容,低于容量的 1 / 4 1/4 1/4会进行缩容
        • 开销:slice是基于数组的申请和复制实现的动态性的,会有开销
      • 映射类型(map):无序的键值对集合
      • 函数类型(func):可以将函数看成一种类型,用于变量的声明
      • 通道类型(channel):用于协程间安全同步的传送数据
    • 接口类型(interface):表示一组不实现的方法集合
      • 错误类型(error):表示错误信息的预定义的接口类型
  3. 介绍一下nil
    • 表示类型声明的变量未被初始化或赋值,
    • 注意
      • 不同类型的nil进行比较需要通过相应的类型判断函数
      • 如果指针类型未初始化,尝试对nil进行解引用会导致panic
  4. 介绍一下Go的异常类型
    • 定义:预定义的error接口类型
    • 作用:用error类型代替try…catch语句,节省资源,增加代码可读性
    // error接口定义
    type error interface {Error() string
    }
    // 错误示例
    package main
    import ("fmt""errors"
    )
    func divide(a, b int) (int, error) {//健壮性检查if b == 0 {	return 0, errors.New("division by zero")}return a / b, nil
    }
    func main() {result, err := divide(10, 2)	// 注意类型的接收if err != nil {fmt.Println("Error:", err)} else {fmt.Println("Result:", result)}
    }
    

二、Go的执行

概述

  1. Go程序的执行由两部分组成
    • Program:用于处理用户输入和执行用户态下的相关操作
    • Runtime:帮助用户程序处理与内核相关的系统调用,通过调度器scheduler提高执行效率
      在这里插入图片描述

调度器scheduler

  1. 定义
    • Go调度器通过GPM机制实现M:N的调度模型,提高垃圾回收效率,实现高效的并发编程。
  2. GPM机制
    • Goroutine(G)代表Go语言的协程,是调度的基本单位
    • Processor(P)代表执行Goroutine的上下文环境及资源,是中间调度器
    • Machine(M)是通过系统调用int 0x56创建的内核线程的抽象
    • 关系
      • 每个Go程序只有一个GPM调度器schedt
      • 每个M绑定一个内核线程
      • M可以关联多个P:因为P维护了上下文,可以在M绑定的内核线程阻塞的情况下,切换到另一个M上执行
      • P可以调度多个G:通过每个P本地就绪的G队列所有P共享的全局G队列实现
    • 池化优化:每个G、P和M都有自己的free队列,用于存储空闲的G/P/M对象,用时直接取,释放时放回,避免频繁的拷贝和销毁开销
    • P的数量:可通过runtime.GOMAXPROCS函数进行设定,默认为当前系统的CPU核数。
      在这里插入图片描述
      在这里插入图片描述
  3. M:N调度模型
    • 原理:N个协程G通过调度器P管理,从而映射到M个内核线程M上运行
    • 作用:平衡内核线程负载,充分提高系统资源的利用率
  4. 初始化执行过程
    • 执行schedinit 函数:初始化调度器相关的参数
    • 初始化 g0 栈:为运行 runtime 代码提供一个“环境”
    • 主线程绑定并初始化m0
    • 编译器将go func() {}翻译成newproc函数
    • newproc:获取函数的参数和代码段地址,通过g0创建G
    • newproc 函数获取了参数和当前g的pc信息,并通过g0调用newproc1去真正的执行创建或获取可用的g
    • newporc1 的作用就是创建或者获取一个空间的g,初始化这个g,并通过gfget尝试寻找一个p和m去执行g
      在这里插入图片描述
      在这里插入图片描述

Goroutine

  1. 定义:Goroutine是一种轻量级的并发实现方式,使用管道机制(channle)进行不同Goroutine间的通信,按照算法将goroutine分配到多个线程上执行,从同高效的利用多核处理器的并发性。
  2. Goroutine的组成
    • 栈帧(BP:SP):用于保存函数调用相关信息
    • 程序计数器(PC):指向当前正在执行的执行地址
    • 执行状态(State):表示当前Goroutine的运行状态
    • 其他基本属性信息,如抢占标记、链式指针、id等
  3. 特点
    • 轻量级(协程与内核线程的区别)
      • 动态小栈:内核线程栈空间通常2MB,而每个Goroutine的初始栈空间只有2KB,并能通过分段栈进行动态扩展,以适应程序的需求。
      • 上下文切换开销小:协程上下文切换只需在用户态下进行三个寄存器(PC、SP和BP)的切换。而线程上下文切换需要陷入内核,并进行16个寄存器的切换,大概5倍的性能开销。
    • 管道通信(channel)
      • 作用:channel是不同Goroutine间安全的数据传递同步机制
      • 组成:环形队列缓冲区和读写等待队列
      • 从channel读数据时,若channel缓冲区为空或没有缓冲区,则阻塞当前读线程,并加入到recvq队列中 。
      • 向channel写数据时,若缓冲区已满或没有缓冲区,则阻塞当前写线程,加入sendq队列中
    • M:N协程调度模型
      • 作用:将M个用户态线程(协程)映射到N个的内核线程上运行的调度模型。从而充分利用多核处理器的性能,同时减少线程上下文切换的开销。
      • 工作窃取(Work Stealing):当P的本地队列为空时,从其他P的本地队列偷取G运行
      • 调度器退化(Scheduler Pacing):通过抢占式调度,调度器中断并切换到其他协程。从而避免长时间运行的任务(例如计算密集型的任务)导致其他协程的饥饿现象
  4. goroutine的状态机模型
    • 创建:Go 必须对每个运行着的线程上的 Goroutine 进行调度和管理。这个调度的功能被委托给了一个叫做 g0 的特殊的 goroutine, g0 是每个 OS 线程创建的第一个goroutine。
    • 终止:在创建 goroutine 时,Go在开启实际go执行片段之前,通过PC寄存器设置了SP寄存器的首个函数栈帧(名为goexit的函数),这个技巧强制goroutine在结束工作后调用函数goexit。

Processor

  1. 定义:存储就绪状态的G队列,并调度和管理goroutine的执行,以实现高效的并发执行和资源利用
  2. P的相关队列
    • RunQueue:每个P拥有一个存储就绪状态(runnable)G的队列,避免对锁竞争激烈的全局队列的直接依赖
    • GlobalQueue:所有P共享同一个存储就绪状态G的队列,并由互斥锁进行并发访问的同步
  3. 作用
    • 维护本地的就绪Goroutine队列,并通过调度算法选择合适的Goroutine到M上执行
    • 管理资源:通过互斥锁同步对于共享资源的并发执行
    • 协调和通信:p会与其他p进行协调和通信,比如在负载均衡时,负责将Goroutine调度到其他p上执行,以充分利用系统的资源。
  4. P的调度算法
    • 新建的G会优先加入到P的本地队列。
    • P的本地队列满了:则将本地队列中一半的G移动到全局队列
    • M获取G的优先级
      • 从P的本地队列获取Goruntine
      • 从P共享的全局队列获取Goruntine
      • 处理网络IO阻塞的网络轮询器获取可运行的G
      • 其它的P的本地队列窃取Goroutine
        在这里插入图片描述

Machine

  1. 定义:是一个虚拟的执行环境,负责执行和管理Goroutine。
  2. 作用:
    • 执行Goroutine
    • 与内核线程进行交互,调度器根据系统负载动态创建或销毁M
    • 若Goroutine 执行时发生系统调用和阻塞,M会将该G标记阻塞状态,并交于调度器进行处理

监控

  1. 定义:在启动阶段,创建一个独立的M执行sysmon函数,不需要依赖P
  2. 原理
    • 调度器创建一个独立M执行sysmon函数
      • 释放闲置超过5分钟的span物理内存
      • 如果超过两分钟没有执行垃圾回收,则强制执行
      • 将长时间未处理的netpoll结果添加到任务队列
      • 向长时间运行的g进行抢占
      • 收回因为syscall而长时间阻塞的p
    • 监控线程首次休眠20us,每次执行完后,增加一倍的休眠时间,但是最多休眠10ms

三、垃圾回收

概述

  1. 定义:垃圾回收(Garbage Collection,简称GC)是一种自动内存管理机制,
    • 当开始垃圾回收时,运行时只需要等待当前正在CPU核上运行的那个Goroutine停止即可,而不需要等待所有的Goroutine。这样可以大大减少阻塞的时间,提高垃圾回收的效率。
  2. Go语言的GC流程(并发标记清除算法)
    • 标记阶段(Marking Phase):
    • 在标记阶段和再标记阶段,通过STW机制暂停所有的Go程来确保标记的准确性;
    • 在清除阶段和内存整理阶段,采用并发方式来提高垃圾回收的效率。
  3. GC的触发条件
    • 主动触发:通过调用 runtime.GC 来触发,但若有正在执行的GC,会阻塞等待
    • 被动触发:
      • 系统监控:当超过两分钟没有产生任何 GC 时,强制触发 GC。
      • 步调算法:?
  4. 如果内存分配速度超过了标记清除的速度怎么办?
    • 目前的 Go 实现中,当 GC 触发后,会首先进入并发标记的阶段。并发标记会设置一个标志,并在 mallocgc 调用时进行检查。当存在新的内存分配时,会暂停分配内存过快的那些 goroutine,并将其转去执行一些辅助标记(Mark Assist)的工作,从而达到放缓继续分配、辅助 GC 的标记工作的目的。
    • 编译器会分析用户代码,并在需要分配内存的位置,将申请内存的操作翻译为 mallocgc 调用,而 mallocgc 的实现决定了标记辅助的实现,
  5. GC的性能指标
    • CPU占用率:回收算法执行占用的CPU时间
    • GC停顿的时间和频率:需要考虑 STW 和 Mark Assist 两个部分可能造成的停顿
    • GC可扩展性:堆增大时,垃圾回收器的性能
  6. Go 的 GC 如何调优
    • 对停顿敏感:由于GC的执行,导致用户代码执行的滞后。优化用户代码从而减少分配内存的数量
    • 对资源消耗敏感:GC增加了对CPU的占用率
    • 原则
      • 避免过早优化,并只优化性能瓶颈
    • 减少内存申请次数(降本)
    • 提高内存申请速度(增效)
      • 池化算法:预申请和复用提高申请速度
  7. Go 的垃圾回收器有哪些相关的 API?其作用分别是什么?
    • runtime.GC:手动触发 G
    • runtime.ReadMemStats:读取内存相关的统计信息,其中包含部分 GC 相关的统计信
    • debug.FreeOSMemory:手动将内存归还给操作系
    • debug.ReadGCStats:读取关于 GC 的相关统计信
    • debug.SetGCPercent:设置 GOGC 调步变
    • debug.SetMaxHeap(尚未发布[10]):设置 Go 程序堆的上限值

算法原理

Go语言中的垃圾回收(GC)算法采用了三色标记(tricolor marking)算法,基于并发标记清除(concurrent mark and sweep)策略。

三色标记算法将所有对象分为三个颜色:白色、灰色和黑色。

白色:表示对象尚未被访问和标记。
灰色:表示对象已经被访问,但是其引用尚未全部扫描。
黑色:表示对象已经被访问,并且其引用已经全部扫描。
GC 运行的步调由两个阶段组成:标记(marking)和清除(sweeping)。

标记阶段:

标记阶段以根对象(root object)为起点,递归地遍历所有可达对象,并将其标记为灰色。
将标记为灰色的对象标记为黑色,并将其引用对象标记为灰色。
重复上述过程,直到没有灰色对象为止。
清除阶段:

从堆中的所有对象中,找到没有标记为黑色的对象,即为垃圾对象。
将垃圾对象释放或回收,并将其空闲内存归还给堆。
Go语言中的GC算法是基于并发标记清除的,即在标记阶段和清除阶段都可以和用户程序同时进行。这样可以最大程度地减小GC对程序性能的影响,并且尽量保持内存的使用效率。

总结起来,Go语言中的垃圾回收(GC)采用了三色标记算法,通过并发标记清除策略进行垃圾对象的标记和释放。这种算法可以高效地管理内存,并且尽量减少对程序性能的影响。

语言比较
  1. 是否具有原生GC
    • 原生GC:Python、Java、JavaScript、Objective-C、Swift
    • 手动内存管理:C、C++、Rust
  2. 垃圾回收的优劣
    • 优点:编程简单,无需程序员手动释放,减少内存泄漏等相关问题、
    • 缺点:具有额外的性能开销,仍然可能存在内存泄漏问题
  3. Java的GC原理
    • 分布式GC:将对象依据存活时间分配到不同的区域,每次回收只回收其中的一个区域。
    • 操作
      • 将堆分成年轻代、老年代和永久代,触发条件是用户的配置和实际代码行为的预测
      • 年轻代收集周期:只对年轻代对象进行收集与清理
      • 老年代收集周期:只对老年代对象进行收集与清理
      • 混合式收集周期:同时对年轻代和老年代进行收集与清理
      • 完整 GC 周期:完整的对整个堆进行收集与清理
  4. 目前 Go 语言的 GC 还存在哪些问题
    • Mark Assist 停顿时间过长
    • Sweep 停顿时间过长
    • 由于 GC 算法的不正确性导致 GC 周期被迫重新执行
    • 创建大量 Goroutine 后导致 GC 消耗更多的 CPU

二、编译

  1. 逃逸分析
    • 定义:在编译原理中,分析指针动态范围的方法。
    • 发生条件:当一个对象的指针被多个方法或线程引用
    • 模糊化处理:Go语言的逃逸分析是编译器执行静态代码分析后,对内存管理进行的优化和简化,它可以决定一个变量是分配到堆还栈上。所以堆和栈的区别对程序员“模糊化“了
  2. 基本原则
    • 函数返回对一个变量的引用
    • 编译器会分析代码的特征和代码生命周期,Go中的变量只有在编译器可以证明在函数返回后不会再被引用的,才分配到栈上,其他情况下都是分配到堆上。
    • 编译器通过分析代码来决定将变量分配到何处。
  3. 编译器会根据变量是否被外部引用来决定是否逃逸:
    • 如果函数外部没有引用,则优先放到栈中;
    • 如果函数外部存在引用,则必定放到堆中;
    • 原因:栈的分配和执行比堆快,如果无法在栈上分配,则会逃逸到堆上。但是堆的分配会增加GC压力
  4. GoPath 的作用在于提供一个可以寻找 .go 源码的路径,包含
    • src 存放源文件
    • pkg 存放源文件编译后的库文件,后缀为 .a
    • bin 则存放可执行文件。
  5. Go的编译过程
    • 词法分析、语法分析
    • 中间代码的生成与优化
    • 目标代码的生成与优化
    • 链接
      在这里插入图片描述


少年,我观你骨骼清奇,颖悟绝伦,必成人中龙凤。
不如点赞·收藏·关注一波


🚩点此跳转到首行↩︎

参考博客

  1. 知乎goroutine解释
  2. 知乎goroutine原理
  3. 协程管道通信机制
  4. golang 源码学习之GMP (goroutine)
  5. 深入理解Go-goroutine的实现及Scheduler分析
  6. Go 程序员面试笔试宝典
  7. Golang并发编程-GPM协程调度模型原理及结构分析
  8. 待定引用

这篇关于【Go语言基础】调度器模型GPM与垃圾回收器GC的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1098680

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

离心萃取机废旧磷酸铁锂电池回收工艺流程

在废旧磷酸铁锂电池的回收工艺流程中,离心萃取机主要应用于萃取除杂的步骤,以提高回收过程中有价金属(如锂)的纯度。以下是结合离心萃取机应用的废旧磷酸铁锂电池回收工艺流程: 电池拆解与预处理 拆解:将废旧磷酸铁锂电池进行拆解,分离出电池壳、正负极片、隔膜等部分。破碎与筛分:将正负极片进行破碎处理,并通过筛分将不同粒径的物料分开,以便后续处理。 浸出与溶解 浸出:采用适当的浸出工艺(如二段式逆

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

搭建Kafka+zookeeper集群调度

前言 硬件环境 172.18.0.5        kafkazk1        Kafka+zookeeper                Kafka Broker集群 172.18.0.6        kafkazk2        Kafka+zookeeper                Kafka Broker集群 172.18.0.7        kafkazk3

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费