线程栈(thread stacks)介绍

先回顾下linux的内存空间布局

简书_stack02.png

当启动一个C实现的thread时,C标准库会负责分配一块内存作为这个线程的栈。标准库分配这块内存,告诉内核它的位置并让内核处理这个线程 的执行。
在linux系统中,可通过 ulimit -s查看系统栈大小(8M)。
ulimit -s 10240可修改栈大小为10M。

这里最大的一个问题是,分配大数组,或者循环递归函数时,默认的栈空间不够用,会导致Segmentation fault错误。

//testMaxStack.cpp
#include <stdio.h> int main() { printf("init ok\n"); char a[8192*1024]; // 8M空间 printf("run over\n"); } //执行结果 [app@VM_114_13_centos c]$ ulimit -s 8192 [app@VM_114_13_centos c]$ g++ testMaxStack.cpp [app@VM_114_13_centos c]$ ./a.out Segmentation fault 

解决方法有两个:

  • ulimit -s 10240调整标准库给所有线程栈分配的内存块的大小。但是全线提高栈大小意味着每个线程都会提高栈的内存使用量,这样一来,你将用光所有内存。
  • 为每个线程单独确定栈大小。这样一来你就不得不完成这样的任务:根据每个线程的需要,估算它们的栈内存的大小。这将是创建线程的难度超出我们的期望。

Go是如何应对这个问题的

Go使用的解决方案类似第二种方法。
goroutine 初始时只给栈分配很小的空间,然后随着使用过程中的需要自动地增长。这就是为什么Go可以开千千万万个goroutine而不会耗尽内存。
Go 1.4开始使用的是连续栈,而这之前使用的分段栈

分段栈(Segmented Stacks)

分段栈(segmented stacks)是Go语言最初用来处理栈的方案。
当创建一个goroutine时,Go运行时会分配一段8K字节的内存用于栈供goroutine运行使 用。

每个go函数在函数入口处都会有一小段代码,这段代码会检查是否用光了已分配的栈空间,如果用光了,这段代码会调用morestack函数。

morestack函数

morestack函数会分配一段新内存用作栈空间,接下来它会将有关栈的各种数据信息写入栈底的一个struct中(下图中Stack info),包括上一段栈的地址。然后重启goroutine,从导致栈空间用光的那个函数(下图中的Foobar)开始执行。这就是所谓的“栈分裂 (stack split)”。

  +---------------+|               ||   unused      ||   stack       || space | +---------------+ | Foobar | | | +---------------+ | | | lessstack | +---------------+ | Stack info | | |-----+ +---------------+ | | | +---------------+ | | Foobar | | | | <---+ +---------------+ | rest of stack | | | 
lessstack函数

在新栈的底部,插入了一个栈入口函数lessstack。设置这个函数用于从那个导致我们用光栈空间的函数(Foobar)返回时用的。当那个函数(Foobar)返回时,我们回到lessstack(这个栈帧),lessstack会查找 stack底部的那个struct,并调整栈指针(stack pointer),使得我们返回到前一段栈空间。这样做之后,我们就可以将这个新栈段(stack segment)释放掉,并继续执行我们的程序了。

分段栈的问题

栈缩小是一个相对代价高昂的操作。如果在一个循环中调用的函数遇到栈分裂 (stack split),进入函数时会增加栈空间(morestack 函数),返回并释放栈段(lessstack 函数)。性能方面开销很大。

连续栈(continuous stacks)

go现在使用的是这套解决方案。
goroutine在栈上运行着,当用光栈空间,它遇到与旧方案中相同的栈溢出检查。但是与旧方案采用的保留一个返 回前一段栈的link不同,新方案创建一个两倍于原stack大小的新stack,并将旧栈拷贝到其中
这意味着当栈实际使用的空间缩小为原先的 大小时,go运行时不用做任何事情。
栈缩小是一个无任何代价的操作(栈的收缩是垃圾回收的过程中实现的.当检测到栈只使用了不到1/4时,栈缩小为原来的1/2)。
此外,当栈再次增长时,运行时也无需做任何事情,我们只需要重用之前分配的空闲空间即可。

如何捕获到函数的栈空间不足

Go语言和C不同,不是使用栈指针寄存器和栈基址寄存器确定函数的栈的。

在Go的运行时库中,每个goroutine对应一个结构体G,大致相当于进程控制块的概念。这个结构体中存了stackbasestackguard,用于确定这个goroutine使用的栈空间信息。每个Go函数调用的前几条指令,先比较栈指针寄存器跟g->stackguard,检测是否发生栈溢出。如果栈指针寄存器值超越了stackguard就需要扩展栈空间。

旧栈数据复制到新栈

旧栈数据复制到新栈的过程,要考虑指针失效问题。
Go实现了精确的垃圾回收,运行时知道每一块内存对应的对象的类型信息。在复制之后,会进行指针的调整。具体做法是,对当前栈帧之前的每一个栈帧,对其中的每一个指针,检测指针指向的地址,如果指向地址是落在旧栈范围内的,则将它加上一个偏移使它指向新栈的相应地址。这个偏移值等于新栈基地址减旧栈基地址

链接:https://www.jianshu.com/p/7ec9acca6480

转载于:https://www.cnblogs.com/mafeng/p/10305419.html

深入理解golang 的栈相关推荐

  1. linux 虚拟机大量udp请求失败_理解 Linux 网络栈:Linux 网络协议栈简单总结分析...

    1. Linux 网络路径 1.1 发送端 1.1.1 应用层 (1) Socket 应用层的各种网络应用程序基本上都是通过 Linux Socket 编程接口来和内核空间的网络协议栈通信的.Linu ...

  2. 深入理解Golang 编程思维和工程实战

    | 导语 Golang 的一些编程思维和思想,以及总结一些常见的优雅编程实战技巧 目录 一 Golang 编程思维 二 Golang 高级编码技巧 1 优雅的实现构造函数编程思想 2 优雅的实现继承编 ...

  3. 【转】深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈

    理解堆与栈 导航 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈 深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 ...

  4. 深入理解Golang之context

    深入理解Golang之context context是Go并发编程中常用到一种编程模式.本文将从为什么需要context,深入了解context的实现原理,以了解如何使用context. 作者:Tur ...

  5. java方法的理解、调用栈与异常处理

    java方法的理解.调用栈与异常处理 参考文章: (1)java方法的理解.调用栈与异常处理 (2)https://www.cnblogs.com/yangxiansen/p/7860058.html ...

  6. 堆和栈的理解 堆和栈的区别 什么是堆和栈 堆是什么 栈是什么

    场景出现于; 在列表(表格)渲染的时候,渲染完毕 点击表格中的每个修改按钮 定义一个空对象,this.details = {} 拿到每个表格中的item[i] 赋值给详情 this.details = ...

  7. golang 函数调用栈

    golang函数调用栈 文章目录 golang函数调用栈 函数栈帧 函数跳转与返回 如果在一个函数中调用另一个函数,编译器就会对应生成一条call指令,程序执行到这条指令时,就会跳转到被调用函数入口处 ...

  8. 深入理解Java的栈与堆栈

    我想这篇足以让大家很清晰理解Java的栈和堆栈的区别.下面的是我收集了好多网友的资料加以整理的. Java 中的堆和栈  Java把内存划分成两种:一种是栈内存,一种是堆内存. 1.栈(stack)与 ...

  9. 深入理解Golang中的Context包

    context.Context是Go语言中独特的设计,在其他编程语言中我们很少见到类似的概念.context.Context深度支持Golang的高并发. 1. Goroutine和Channel 在 ...

最新文章

  1. python打开界面是什么样的-python学习笔记(图形用户界面)
  2. 社群分享:涨粉的35个玩法和技巧
  3. appium java版本错误_java – 无法创建新会话. appium代码中的错误
  4. MaxCompute(原ODPS)使用总结-初级篇
  5. plsql job执行多个存储过程_spring-boot-micro-job一款分布式任务调度执行框架
  6. SAP UI5 busy Dialog debug
  7. python消息中间件有哪些_消息中间件选型
  8. 【软件开发底层知识修炼】十一 链接器-链接脚本
  9. STM32----摸石头过河系列(六)
  10. 近一个月来的学习总结(今天的你比昨天的你进步了吗?)
  11. 动态创建数据表php,PHP实现动态添加XML中数据的方法
  12. 无效内卷正在毁掉年轻一代程序员
  13. Linux 删除除某个文件之外的所有文件
  14. 4.5 NiN CNN、tensorflow实现——python实战
  15. cpp Namespaces(命名空间)
  16. AutoCAD2006软件下载AutoCAD2006安装方法
  17. CTF之misc-图片隐写
  18. 处理txt文件下载下来以后,排版格式不对的问题
  19. 【安全知识分享】2021年安全生产月主题宣讲课件(附下载)
  20. 佳音图php,PHP5实例教程 简简单单生成条形码

热门文章

  1. WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化
  2. 如何定义一个只能在堆上(栈上)生成对象的类?
  3. Android ListView性能优化实例讲解
  4. PHP 如何判断当前用户已在别处登录
  5. MR作业的提交监控、输入输出控制及特性使用
  6. CodeVS 1044 拦截导弹(DP)
  7. Ubuntu用户Steam控制器不工作的解决办法
  8. 阿里云centos 安装和配置 DokuWiki
  9. node.js 实现扫码二维码登录
  10. 不要学习代码,要学会思考(转)