【翻译】为什么 goroutine 的栈内存无穷大?

一些 Go 语言的新学习者总是会对 goroutine 栈内存占用大小感到非常好奇。这一般是由于程序员进行无限的函数循环调用导致的。为了说明这个问题,请思考以下代码示例(为使问题更加清晰而使用相对刻意的写法):

package mainimport "fmt"type S struct {
        a, b int}// String 实现了接口 fmt.Stringerfunc (s *S) String() string {        return fmt.Sprintf("%s", s) // 调用 Sprintf 时会默认调用 s.String()}func main() {
        s := &S{a: 1, b: 2}
        fmt.Println(s)
}

尽管我不建议你这样做,但当你尝试运行这段代码的时候,你会发现你的机器正在进行大量的运算,甚至变得无响应而使你不得不使用 ctrl + c 来中断执行,以免程序最终达到无药可救的地步;因为我知道你会这样做,所以我为你做好了这一步,你可以直接在 playground 执行这段代码。

许多程序员都曾经写过类似的代码而导致函数的无限循环调用,并使得他们的程序崩溃,但一般情况下并不足以对他们的机器造成毁灭性破坏。问题是,为什么 Go 的程序就特殊一点的呢?

goroutine 的一个主要特性就是它们的消耗;创建它们的初始内存成本很低廉(与需要 1 至 8MB 内存的传统POSIX 线程形成鲜明对比)以及根据需要动态增长和缩减占用的资源。这使得 goroutine 会从 4096 字节的初始栈内存占用开始按需增长或缩减内存占用,而无需担心资源的耗尽。

为了实现这个目标,链接器(5l、6l 和 8l)会在每个函数前插入一个序文,这个序文会在函数被调用之前检查判断当前的资源是否满足调用该函数的需求(备注 1)。如果不满足,则调用 runtime.morestack 来分配新的栈页面(备注 2),从函数的调用者那里拷贝函数的参数,然后将控制权返回给调用者。此时,已经可以安全地调用该函数了。当函数执行完毕,事情并没有就此结束,函数的返回参数又被拷贝至调用者的栈结构中,然后释放无用的栈空间。

通过这个过程,有效地实现了栈内存的无限使用。假设你并不是不断地在两个栈之间往返,通俗地讲叫栈分割,则代价是十分低廉的。

但是我一直注意到一个问题,当你的程序存在函数的无限循环调用而即将导致你的操作系统内存枯竭,而此时又恰好需要分配新的栈页面,则会从堆中分配内存。

当你的函数无止尽地调用着自己,新的栈页面会不断地从堆中分配,继而使得函数又能够继续调用自己。我相信这很快就会使程序用光你机器所有空余的物理内存,交换存储器也会被大量使用,最终导致你的系统变得非常不稳定。

可以被 Go 使用的堆内存取决于许多方面,包括你的 CPU 架构以及操作系统,但一般依赖于你机器可用的物理内存,因此你的机器会在即将使用完堆内存之前进行大量交换存储器的操作。

对于 Go 1.1,许多人都希望可以提升 32 位以及 64 位平台上堆内存使用的最大限制,这个问题会在某些情况下变得更加严重。比如说,你的机器不太可能拥有 128GB 的物理内存(备注 3)。

最后要说的是,这里有一些 issue 已经涉及到这个问题(issue1issue2),但仍未找到在不损失性能的情况下能够处理该问题的一个好的解决方案。

备注: 1. 同样适用于方法,但方法的接收者本质上就是函数的第一个参数,当讨论有关 Go 的分段栈的问题时,没有必要将它们区别对待。 2. 使用页面这个词不代表每次分配的内存额度是固定的 4096 字节,必要时会调用 runtime.morestack 来进行新的分配,但我猜测会与页面值的倍数相接近。 3. 由于 Go 1.1 的改动,64 位 Windows 平台的堆内存被限制在 32GB 之内。

原文发布于微信公众号 - Golang语言社区(Golangweb)

原文发表时间:2016-08-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏禁心尽力

多线程之策略模式

今天给各位分享一种Java23种设计模式中最常见的设计模式--策略模式。为什么将策略模式和多线程绑在一起呢,不知道各位有没有注意过我们在进行多线程编程的时候,创...

2127
来自专栏Golang语言社区

无辜的goroutine

简介: 本文主要是针对一些对于goroutine的“指控”提出我自己的看法,特别是轩脉刃的一篇博客文章《论go语言中goroutine的使用》提出了gorout...

36511
来自专栏大内老A

WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[上篇]

对于上一篇文章 (WCF基本异常处理模式:[上篇]、[中篇]、[下篇]),主要是站在最终开发者的角度对WCF关于异常处理编程模式进行了介绍,接下来,我们需要将我...

2069
来自专栏编程

C语言嵌入式系统编程修炼之性能优化

这是我13年前创作和发表在互联网上的文章,这么多年过去了,这篇文章仍然在到处传播。现在贴回Linuxer公众号。 全文目录: C语言嵌入式系统编程修炼之道——背...

2577
来自专栏葡萄城控件技术团队

C# 8.0的三个值得关注的新特性

image.png C# 语言是在2000发布的,至今已正式发布了7个版本,每个版本都包含了许多令人兴奋的新特性和功能更新。同时,C# 每个版本的发布都与同...

2678
来自专栏葡萄城控件技术团队

C# 8.0的三个值得关注的新特性

1353
来自专栏python学习路

一、代码风格 1、假定你的代码需要维护2、保持一致性3、考虑对象在程序中存在的方式,尤其是那些带有数据的对象4、不要做重复工作5、让注释讲故事6、奥卡姆剃刀原则1、简洁的规则2、文档字符串3、空行4、

刚开始学的时候就要注意编码规范了,所以整理了一下,以便养成一个编码好习惯。不然以后真的不好改。 代码被读的次数远大于被写的次数。 作为一名程序员(使用任何语言)...

2445
来自专栏C/C++基础

C++为什么要引入异常处理机制

在程序设计中,错误时不可避免的。及时有效的发现错误,并作出适当的处理,无论是在软件的开发阶段还是在维护阶段都是至关重要的。错误修复技术是提高代码健壮性的最有效的...

911
来自专栏Golang语言社区

无辜的goroutine

简介: 本文主要是针对一些对于goroutine的“指控”提出我自己的看法,特别是轩脉刃的一篇博客文章《论go语言中goroutine的使用》提出了gorout...

3085
来自专栏Urahara Blog

Using get_defined_functions To Hidden A PHP Backdoor

1782

扫码关注云+社区

领取腾讯云代金券