• go协程栈底层讲解


    前面几章内容
    (一) go协程栈底层讲解
    (二) go的堆内存结构分析
    (三) 高级语言垃圾回收思路和如何减少性能影响原理分析

    本节主要分为三节讲解栈内存(go中的协程栈、调用栈)、堆内存、go中的垃圾回收底层相关知识
    这几节知识和go语言在高并发特性息息相关,所以很值得分析分析,同时也能大佬们在设计程序语言时的思想

    1. go的栈在堆上?

    1.1 go 协程栈的作用

    我们之前学习go的协程栈时,有一个程序内部的示意图,也就是下面这个样子。整体区域就是go中的栈区(RAM stack),里面是放go的栈内存的,中间的小块是放go一个协程的协程栈,我们之前学到,一个协程栈的第一个方法是goexit() ,它是为了退出之后重新进行调度用户方法的,后面的就是用户的一个一个方法了。
    在这里插入图片描述
    协程除了上面能记录协程执行的路径,另外还能存储一些信息,比如局部变量、函数传参、函数的返回值。这些都与c/c++ 类似。

    小结一下:go 协程栈的作用

    • 协程的执行路径 (do1—>do2)
    • 局部变量
    • 函数传参
    • 函数返回值

    1.2 go 协程栈的位置

    • go 的协程栈位于go 的堆内存上
    • go堆内存位于操作系统的虚拟内存上(这里是操作系统为每一个进程分配的虚拟内存,不是物理机中虚拟内存的概念)

    1.3 go 协程栈结构

    有这么一段代码, 我们来分析一下里面的协程栈结构
    在这里插入图片描述

    1. 主协程栈

    在这里插入图片描述
    2. 通过runtime.main 进入用户的main,这里的main.main 需要开多大,是由编译器分析的
    在这里插入图片描述
    3. 填入程序的参数,其中函数参数进入栈帧是最后一个参数开始的
    在这里插入图片描述
    4. 程序执行的过程步骤

    在这里插入图片描述

    1.4 参数传递

    从上面的例子分析

    1. Go 使用参数拷贝传递(值传递)
    2. 传递结构体时: 会拷贝结构体中的全部内容(所以在使用结构体时,尽量用指针,虽然也是拷贝传递,但是是拷贝的地址,大大减少了拷贝效率)

    1.5 小结

    1. 协程栈记录了协程的执行现场
    2. 协程栈还负责记录局部变量,传递参数和返回值
    3. Go 使用参数拷贝传递

    1.6 思考

    由于每个协程栈是紧挨着的(下面示意图),那么问题来了

    在这里插入图片描述
    协程栈不够大怎么办? 如果当中有一个协程栈不够大,但是下面有其他的协程栈,这时应该怎么办?

    通过资料发现,这些情况能导致协程栈大

    1. 本地变量太大
    2. 栈帧太多(递归层数过多)(在c中就是栈溢出错误)

    2. 协程栈不够大怎么办呢

    紧接着上一节的思考,学习这一章节

    在go 中协程栈只有2k-4k, 不能和线程的栈大小比(线程用兆M为单位计算的)

    下面通过两个主要原因进行分析学习

    2.1 局部变量太大

    局部变量太大往往会出现逃逸现象,我们针对逃逸现象进行分析

    什么是逃逸分析呢?

    简单来说: 变量是由栈上-----> 堆上(在java语言中貌似也有)

    不是所有的变量都能放在协程栈上(下面是里两个原因)

    1. 栈帧回收后,需要继续使用的变量
    2. 太大的变量

    在逃逸分析,我们需要分析下面三个具体的原因:

    • 指针逃逸
    • 空接口逃逸
    • 大变量逃逸

    2.1.1 指针逃逸

    指的是: 本应该是局部变量的,但是返回给了返回值,这时就放在了堆上

    术语描述: 函数的返回了对象的指针
    在这里插入图片描述

    2.1.2 空接口逃逸

    下面是一个简单的打印代码:,但我们看下println的函数接口,
    在这里插入图片描述
    它里面是一个空接口,所以可能会逃逸
    在这里插入图片描述
    分析:

    1. 如果函数参数为interface{}
    2. 函数的实参很可能会逃逸
    3. 因为interface{}类型的函数往往会使用反射(因为反射要求的对象在堆上,在栈上很难做)

    2.2.3 大变量逃逸

    1. 过大的变量会导致栈空间不足
    2. 在64位机器中,一般超过64KB的变量会逃逸

    2.2 栈帧太多

    在方法中如果出现栈帧太多的情况,程序内部会采用栈扩容方法进行处理
    栈扩容

    1. Go 栈的初始空间为2KB
    2. 在函数调用前判断栈空间(morestack)(协程在调用前这个函数会判断栈空间是否足够)
    3. 必要时对栈进行扩容
    4. 早期使用分段栈,后期使用连续栈(时期指的是go版本 )

    2.2.1 分段栈(1.13版本使用)

    分段栈的情况是: 假如第一个栈帧空间不够,直接使用图中箭头指向的空间

    可以想象一下,当一个栈里面出现大量栈帧空间不够用时,使用这种方法会在不连续的空间来回跳转

    优点: 没有空间浪费
    缺点: 栈指针会再不连续的空间跳转(当两块空间中有返回值时)
    在这里插入图片描述

    2.2.2 连续栈

    连续栈: 直接将原先栈空间不够的拷贝到新开辟的栈空间

    优点: 空间一直连续
    缺点: 伸缩时的开销大

    原理: 当空间不足时扩容,变为原来的2倍;当空间使用率不足1/4时缩容,变为原来的1/2。

    连续栈的示意图:
    在这里插入图片描述

    2.3 小结

    1. 3种特殊情况下,变量可能会分配到堆上
    2. 1.13 之前,Go 使用可伸缩的分段栈
    3. 1.14以后,Go使用连续栈,伸缩时直接使用新栈

    推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

  • 相关阅读:
    【跨境电商】提高客户留存率的 9 种策略
    HTTP协议简介
    【精品】基于jjwt0.11.5的完美的Jwt工具类
    论文解读(GGD)《Rethinking and Scaling Up Graph Contrastive Learning: An Extremely Efficient Approach with Group Discrimination》
    商品API接口优秀案例 │ 国家电网办公物资电商化采购项目API解决方案
    HDFS的Shell操作操作
    【面试经典150 | 数组】多数元素
    2‘,7‘-二-(2-羧乙基)-5(6)-羧基荧光素乙酰甲酯,CAS号: 117464-70-7
    JVM学习(三)-- 垃圾回收
    实现基于 Jenkins 的多服务器打包方案
  • 原文地址:https://blog.csdn.net/qq_39486027/article/details/126049608