惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

G
GRAHAM CLULEY
T
Tenable Blog
Know Your Adversary
Know Your Adversary
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
S
Security Affairs
NISL@THU
NISL@THU
O
OpenAI News
Attack and Defense Labs
Attack and Defense Labs
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Hacker News: Ask HN
Hacker News: Ask HN
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
S
SegmentFault 最新的问题
S
Schneier on Security
G
Google Developers Blog
V
V2EX
C
Check Point Blog
U
Unit 42
Google DeepMind News
Google DeepMind News
T
Threatpost
阮一峰的网络日志
阮一峰的网络日志
T
The Exploit Database - CXSecurity.com
Recent Announcements
Recent Announcements
M
MIT News - Artificial intelligence
S
Secure Thoughts
博客园 - 司徒正美
Recorded Future
Recorded Future
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
K
Kaspersky official blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
AI
AI
博客园 - 聂微东
N
News and Events Feed by Topic
SecWiki News
SecWiki News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Vulnerabilities – Threatpost
P
Palo Alto Networks Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
The Hacker News
The Hacker News
The Last Watchdog
The Last Watchdog
Project Zero
Project Zero
W
WeLiveSecurity
博客园 - Franky

Jiajun的技术笔记

你好,2026! TiDB 源码阅读(六):TiDB Coprocessor 源码解析 性能优化的核心思想 TiDB 源码阅读(五):索引 TiDB 源码阅读(四):AST、逻辑计划、物理计划 CockroachDB Serverless Architecture podman 无故退出 Cursor Control-L (CTRL-L) Keyboard Shortcuts in Terminal Replace docker with podman Using xmonad with xfce4 A RC script for freebsd frpc 自己动手写一个k8s controller AI 会取代你的(编程)岗位吗? 自建DERP服务器提升Tailscale连接速度(使用Nginx转发) 自动升级Docker容器 再读《程序员修炼之道-从小工到专家》 让浏览器下载文件 再读《软件随想录》/《黑客与画家》/《软技能》 HTTP 压力测试中的 Coordinated Omission 2的补码 编程语言中的 context 是什么? flutter macOS 构建出错 Flatpak 使用小记 Golang CAS 操作是怎么实现的 PostgreSQL 当MQ来使用 Clash 结合 工作VPN 的网络设计 使用 PostgreSQL 搭建 JuiceFS PostgreSQL 配置优化和日志分析 有GitHub Copilot?那就可以搭建你的ChatGPT4服务 窗口函数的使用(以PG为例) 读《为什么学生不喜欢上学》 OpenAI Prompt Engineering 摘录和总结 读《打造真正的新产品》 VueJS 总结 Linux 自动挂载 alist 提供的webdav FreeBSD 使用 vm-bhyve 安装Debian虚拟机 FreeBSD 和 Linux 网卡聚合实现提速 GPT 帮我搞定了时区转换问题 长任务系统如何处理? macOS/Linux 编译 InputLeap 使用开源软KVM - synergy-core 解决 macOS 终端hostname一直变化问题 KVM 共享 Intel 集成显卡 PromQL 备忘 读《格鲁夫给经理人的第一课》 读《打开心智》 为什么要把复杂的联表操作拆成多个单表查询? 红包系统的设计 MySQL Index Condition Pushdown Optimization Go mod 简明教程 OpenWRT 使用 Android/iOS USB 网络 搭建旁路由 Golang gRPC 错误处理 编写可维护的单元测试代码 OAuth 2 详解(六):Authorization Code Flow with PKCE OAuth 2 详解(五):Device Authorization Flow OAuth 2 详解(三):Resource Owner Password Credentials Grant OAuth 2 详解(四):Client Credentials Flow OAuth 2 详解(二):Implict Grant Flow OAuth 2 详解(一):简介及 Authorization Code 模式 ElasticSearch 学习笔记 三种git流程以及发版模型 错误处理实践 权限模型(RBAC/ABAC) OIDC(OpenID Connect) 简介 任务队列简介 PostgreSQL 操作笔记 使用Drone CI构建CI/CD系统 Golang migrate 做数据库变更管理 使用PostgreSQL做搜索引擎 Nginx 源码阅读(三): 连接池、内存池 Nginx 源码阅读(二): 请求处理 Nginx 源码阅读(一): 启动流程 Go 泛型简明教程 KVM 显卡穿透给 Windows 使用 HTTP Router 处理 Telegram Bot 按钮回调 使用反射(reflect)对结构体赋值 GIN 是如何绑定参数的 你好 2022(2021 年终总结) 用Go导入大型CSV到PostgreSQL 使用 OpenWRT 搭建软路由 使用软KVM切换器 barrier 共享键鼠 SQL 防注入及原理 使用 gomock 测试 Go 代码 gevent不是黑魔法(二): gevent 实现 gevent不是黑魔法(一): greenlet 实现 用 entgo 替代 gorm 应用内使用crontab不是那么方便 单测时要不要 mock 数据库? Sentry 自建指南 用selenium完成自动化任务 用闲置的安卓手机做垃圾电话短信过滤 推荐三个时间管理工具 一次事故反思 当JS遇到uint64:JS整数溢出问题 SQLite3 存储以及ACID原理 Redis源码阅读:pub/sub实现 Redis源码阅读:zset实现 Redis源码阅读:bitmap 位图的运算 Redis源码阅读:set是怎么做交并集运算的?
Goroutine是如何处理栈的?
Jiajun Huang · 2020-08-26 · via Jiajun的技术笔记

为什么Go的栈是无限大小的?为什么Go的栈策略从 segmented stacks 切换到 contiguous stacks?逃逸分析是什么?这篇文章是我的学习记录,分别解答了上述问题。

Go在1.3以前,是使用一种叫做 segmented stacks 的栈策略。而1.3时,换成了 contiguous stacks ,我们分别来了解一下这两种策略。

segmented stacks

在1.2之前,每一个Goroutine创建的时候,都会申请一个8KB大小的空间作为该Goroutine的栈。当8KB用完时,Go会通过 morestack 函数为之 再申请一块内存,然后把两个栈连起来。

当函数返回时,把新申请的那个栈释放掉。

segmented stacks有这么一个缺点,就是当一个栈快要满时,会申请一个 新的栈来执行子函数,当子函数执行完成时,会把栈回收。 如果不断重复上面这个过程,那么就会出现这个Goroutine频繁的申请和释放栈,因此产生了 “hot split” 问题。

contiguous stacks

为了解决上述问题,Go在1.13时,切换到了新的策略,叫做 “contiguous stacks”,它的策略如下:

  • 当栈不够用时,创建一个更大的栈
  • 把老栈的所有内容copy到新的栈
  • 调整copy过去的内容中的指针地址(把老栈的地址,改成新栈的)
  • 销毁老的栈

为什么能做到第三点呢?要想做到第三点,调整地址,必须有一个先决条件, 那就是栈里的地址,只被栈内使用,而堆里没有使用。 这是为什么呢?想要调整栈内的地址调用,不算难,把栈内的地址减去老栈的起始地址,就是 它们相对于栈的起始位置的偏移量, 然后再加上新栈的起始位置,就可以得到新的内存地址。

然而,在堆里的变量,我们无法知道哪个变量引用了栈内的地址,也就无法更改它的值,所幸,Go使用了一种叫做 “逃逸分析” 的技术,避免了这个 问题。

逃逸分析

在一个编程语言里,变量要么在栈中传递,要么在堆中共享,对于一些只读变量,可能会分配在代码段等其它地方。此处我们只讨论栈和堆。

我们通过一个例子看看Go的逃逸分析报告:

$ cat -n main.go 
     1	package main
     2	
     3	import (
     4		"fmt"
     5	)
     6	
     7	func add(a, b int) int {
     8		result := a + b
     9		fmt.Printf("result is %v\n", &result)
    10		return result
    11	}
    12	
    13	func main() {
    14		result := add(1, 2)
    15		fmt.Printf("result is %v\n", &result)
    16	}
$ go build -gcflags "-m -l" && ./test 
# _/home/jiajun/Code/test
./main.go:8:2: moved to heap: result
./main.go:9:12: ... argument does not escape
./main.go:14:2: moved to heap: result
./main.go:15:12: ... argument does not escape
result is 0xc000014108
result is 0xc000014100

可以看到,第8行和第14行的result变量,分别都逃逸到了堆。

对于逃逸分析的结果,可以用这么一句话简单概括:函数内,变量如果没有被传到函数外,就没有逃逸,否则,则逃逸。对于逃逸的变量,分配到堆上,否则,分配到栈上。

上面逃逸了,是因为传给 fmt.Printf 时,传递了 result 的地址,我们改掉再来看看:

$ cat -n main.go 
     1	package main
     2	
     3	import (
     4		"fmt"
     5	)
     6	
     7	func add(a, b int) int {
     8		result := a + b
     9		return result
    10	}
    11	
    12	func main() {
    13		result := add(1, 2)
    14		fmt.Printf("result is %v\n", result)
    15	}
$ go build -gcflags "-m -l" && ./test 
# _/home/jiajun/Code/test
./main.go:14:12: ... argument does not escape
./main.go:14:13: result escapes to heap
result is 3

可以看到 add 函数内就没有发生逃逸了。

那么逃逸分析有什么用呢?通过逃逸分析,我们可以对变量的位置分配,提前进行优化,没有逃逸的分配在栈上,逃逸的在堆上。这洋酒可以减小 GC的压力,因为堆上的东西少了,GC就快了。

同时,我们还可以获得一个加成效果,那就是 contiguous stacks 对栈内 变量的要求。

逃逸分析的规则还很复杂,以上只是一个简述(比如被Go的fmt.Printf函数引用了,那就一定会逃逸等等)。

继续讲contiguous stacks

我们现在知道,Go通过逃逸分析技术,为我们提供了一个重要的保证:

the only pointers to data in a stack, are in that stack themselves (there are some exceptions though). If any pointer escapes (eg. the pointer is returned to the caller) it means that the pointed data are allocated on the heap instead.

因此,第三点可以行得通,整个计划就行得通。

这样带来的好处,就是不再有 “hot split” 问题,Goroutine的栈变大了之后, 不再收缩,而是一直保持这个大小,直到Goroutine被回收。

结论

这篇文章中我们学习了Go的栈管理策略,了解了他们的异同,了解了逃逸分析技术,以及它为Go切换栈管理策略奠定的基础。


Ref: