当前位置: 首页 > news >正文

【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. 待定引用

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • GNU/Linux - RSYSLOG
  • 基于大数据分析景区消费行为影响因素研究【消费等级预测、携程,去哪网数据抓取】
  • 去雾去雨算法
  • 力扣top300:1.两数之和
  • 37-RPC HTTP区别是什么
  • 用于目标说话人提取的统一视听线索
  • CSS3 3D 转换
  • GPT-6曝光!阉割版「草莓」秋季兑现
  • qtcreator的vim模式下commit快捷键ctrl+g,ctrl+c没有反应的问题
  • labelImg使用
  • 基于网络技术的天气数据查询
  • 在 Spring Boot 中为 MyBatis 添加拦截器
  • .Net 6.0--通用帮助类--FileHelper
  • python基础语法1
  • JavaSE ——类和对象
  • [译]Python中的类属性与实例属性的区别
  • conda常用的命令
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • JS函数式编程 数组部分风格 ES6版
  • mysql常用命令汇总
  • MySQL数据库运维之数据恢复
  • supervisor 永不挂掉的进程 安装以及使用
  • 基于webpack 的 vue 多页架构
  • 计算机常识 - 收藏集 - 掘金
  • 理解IaaS, PaaS, SaaS等云模型 (Cloud Models)
  • 全栈开发——Linux
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 无服务器化是企业 IT 架构的未来吗?
  • 详解NodeJs流之一
  • 一文看透浏览器架构
  • 用Canvas画一棵二叉树
  • C# - 为值类型重定义相等性
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • (16)Reactor的测试——响应式Spring的道法术器
  • (c语言)strcpy函数用法
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (没学懂,待填坑)【动态规划】数位动态规划
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (三)SvelteKit教程:layout 文件
  • (中等) HDU 4370 0 or 1,建模+Dijkstra。
  • (轉貼) 資訊相關科系畢業的學生,未來會是什麼樣子?(Misc)
  • .net 获取url的方法
  • .NET/ASP.NETMVC 深入剖析 Model元数据、HtmlHelper、自定义模板、模板的装饰者模式(二)...
  • .NET/C# 使用 SpanT 为字符串处理提升性能
  • .netcore 如何获取系统中所有session_ASP.NET Core如何解决分布式Session一致性问题
  • .NET轻量级ORM组件Dapper葵花宝典
  • .net中生成excel后调整宽度
  • @Data注解的作用
  • @JsonFormat与@DateTimeFormat注解的使用
  • @param注解什么意思_9000字,通俗易懂的讲解下Java注解
  • [ 常用工具篇 ] POC-bomber 漏洞检测工具安装及使用详解
  • [ 蓝桥杯Web真题 ]-布局切换
  • [Algorithm][综合训练][体育课测验(二)][合唱队形][宵暗的妖怪]详细讲解
  • [Bzoj4722]由乃(线段树好题)(倍增处理模数小快速幂)