📚 Go: Under The Hood | Go 语言原本

logo

Go 语言原本

当前内容基于 go1.15

Go 语言从 2009 年诞生之初已有十余年的历史。 纵观大多数编程语言的历史进程,令人惊讶的是 Go 语言自身在进化的这十余年间, 语言本身并未发生太大变化,Go 语言的用户能够持续不断写出向后兼容的应用。 从语言设计的角度而言,作为一门从诞生之初就考虑低成本、高并发、简洁等原则的语言, 很难让人不对其简洁设计背后的各项实现机制以及具体工作原理所好奇。 本书就是一本讨论 Go 语言源码工程中的技术原理及其演进历程的书籍。

致读者的话

读者可能会好奇,设计总在演进、源码总在变化,为什么要耗费力气研究实际工作中可能永远不会接触的源码? 笔者以为不然,因为『软件工程发生在代码被非原作者阅读之时』,在阅读源码的过程中, 我们除了能进一步加深对语言本身的理解,更重要的则是理解某个设计背后所使用的根本原理, 以及当其他人在实现这个设计的过程中发生的工程决策、实践与实现技巧。 代码总是可以推倒重来,但原理却能『永生』。

本书的创作愿景是涵盖整个 Go 语言的方方面面。这包括用户代码能直接接触的 Go 运行时组件、 与关键语言特性强相关的工具链、诸多重要的标准库等等。在部分情况下, 本书会讨论不同平台下的实现差异,但着重以 Linux/amd64 为主。

阅读的预备知识

阅读本书的读者应该具备一些基本的计算机科学素养,至少学过一门程序设计课程以及数据结构的课程,例如能够熟悉地谈论散列表、红黑树等概念。若你具备基本的离散数学概率论知识,对谓词、随机变量等数学概念具有一定程度的理解,那么将会对阅读本书的部分章节有所帮助。

本书并未要求读者已经掌握使用 Go 语言,因此会在开篇快速介绍 Go 的语言规范。但如果你已经具备 Go 语言编码和相关开发经验,则会对阅读本书有所帮助。

免责声明

注意,目前本书还属于相当早期的创作阶段。如果读者在阅读过程中怀疑某段内容的描述是错误的, 那么它很可能就是错误的。本书目前的大致创作进度:█░░░░░░░░░ (9.9%/100%)

开始阅读

社区的支持

本书的主页( https://golang.design/under-the-hood )以及 GitHub 仓库( https://github.com/golang-design/under-the-hood )上可以找到本书的更新情况以及一些额外的参考资料。 这是一本属于 golang.design initiative 旗下创作的开源书籍,读者还可以在 GitHub 仓库上发起关于本书内容的问题,或报告本书存在的错误,甚至参与创作。 笔者欢迎您在 GitHub 仓库上提交 IssuesPull Request。 其具体细节请参考如何参与贡献。 如果您想要关注本仓库的更新情况,可以点击仓库的 Watch。如果您喜欢本书,我们也非常高兴能够收到您的 Star 和资助。

致谢

本书的主笔(@changkun)首先希望感谢 Go 夜读 的创始人 @yangwenmaigolang.design 计划提供赞助。 其次,我们还希望感谢 Go 夜读 社区小组的核心成员,感谢他们努力建设的 Go 语言社区环境,他们是:@qcrao, @eddycjy, @FelixSeptem,以及社区里的朋友们积极参与并讨论 Go 语言的相关问题,他们是:@cch123

当然,本书的写作离不开诸多热心读者的支持,我们收到了来自下列人员的有帮助的评价和勘误,包括但不限于:@two, @yangxikun, @cnbailian, @choleraehyq, @PureWhiteWu, @hw676018683, @wangzeping722, @l-qing。我们真心感谢这些人对本书内容的质疑与指正。当然,书中还可能有错误存在,希望得到更多的指正和反馈。

最后,特别感谢 @egonelbre 所提供的 Gopher 图片设计。

许可

© 2018-2020 The golang.design Initiative Authors. Licensed under CC-BY-NC-ND 4.0.

Owner
The golang.design Initiative
Hopefully everything about Go!
The golang.design Initiative
Comments
  • ch08gc/barrier: writePointer 疑问

    ch08gc/barrier: writePointer 疑问

    混合写屏障的算法思想伪代码:

    // 混合写屏障
    func writePointer(slot, ptr unsafe.Pointer) {
        shade(*slot)                // 对正在被覆盖的对象进行着色,通过将唯一指针从堆移动到栈来防止赋值器隐藏对象。
        if current stack is grey {  // 如果当前 goroutine 栈还未被扫描为黑色
            shade(ptr)              // 则对引用进行着色,通过将唯一指针从栈移动到堆中的黑色对象来防止赋值器隐藏对象
        }
        *slot = ptr
    }
    

    如果当前 goroutine 栈已扫描为黑色,而 ptr 为白色对象,此时如果不对 ptr 着色,是否就误回收了对象?

  • ch06sched/init: P 的状态转换图中 _Pdead 不会切换为 _Prunning

    ch06sched/init: P 的状态转换图中 _Pdead 不会切换为 _Prunning

    实际描述

    • 文件路径:https://github.com/changkun/go-under-the-hood/blob/master/book/zh-cn/part2runtime/ch06sched/init.md
    • 原文段落:

    P 的初始化,P 的状态转换图

    问题描述

    你好,在 procresize 中,对于收缩的 gomaxprocs 值,会将多出的 P 状态置为 _Pdead,同时释放其相关资源,但是不会释放掉 P 本身,这点在下文的 procresize 源码注释中也提到过。

    	// 从未使用的 p 释放资源
    	for i := nprocs; i < old; i++ {
    		p := allp[i]
    		p.destroy()
    		// 不能释放 p 本身,因为他可能在 m 进入系统调用时被引用
    	}
    

    还有一点是 _Pdead 状态应该只可能在 procresize 中转换为 _Pidel,没可能转为 _Prunning

  • book: typo fixes in ch06sched/schedule.md

    book: typo fixes in ch06sched/schedule.md

    说明

    修改拼写错误。

    变化箱单

    • 修复了 book/zh-cn/part2runtime/ch06sched/schedule.md 的 typo 错误
    • 修复了 book/backlog/sched/exec.md 的 typo 错误
    • 修复了 gosrc/runtime/proc.go 的 typo 错误
  • map不能用cas更新value

    map不能用cas更新value

    实际描述

    • 文件路径:under-the-hood/gosrc/sync/map.go
    • 原文段落:L69
    // 一个 entry 可以被原子替换为 nil 来删除:当 m.dirty 是下一个创建的,它会自动将 nil 替换为 expunged 且
    // 让 m.dirty[key] 成为未设置的状态。
    

    预期描述

    // 一个 entry的p字段 可以被原子替换为 nil 来删除:当 m.dirty 是下一个创建的,它会自动将 nil 替换为 expunged 且
    // 让 m.dirty[key] 成为未设置的状态。
    
  • book: improve gfget/gfput behavior description in ch06sched/init

    book: improve gfget/gfput behavior description in ch06sched/init

    resolve #66

    说明

    改进 gfget 中关于 g 的栈分配的注释。

    变化箱单

    • 修正了book/zh-cn/part2runtime/ch06sched/init.md 和 gosrc/runtime/proc.go中gfget代码注释。

    参考文献

    runtime/proc.go

    // Put on gfree list.
    // If local list is too long, transfer a batch to the global list.
    func gfput(_p_ *p, gp *g) {
    	if readgstatus(gp) != _Gdead {
    		throw("gfput: bad status (not Gdead)")
    	}
    
    	stksize := gp.stack.hi - gp.stack.lo
    
    	if stksize != _FixedStack {
    		// non-standard stack size - free it.
    		stackfree(gp.stack)
    		gp.stack.lo = 0
    		gp.stack.hi = 0
    		gp.stackguard0 = 0
    	}
    
    	_p_.gFree.push(gp)
    	_p_.gFree.n++
    	if _p_.gFree.n >= 64 {
    		lock(&sched.gFree.lock)
    		for _p_.gFree.n >= 32 {
    			_p_.gFree.n--
    			gp = _p_.gFree.pop()
    			if gp.stack.lo == 0 {
    				sched.gFree.noStack.push(gp)
    			} else {
    				sched.gFree.stack.push(gp)
    			}
    			sched.gFree.n++
    		}
    		unlock(&sched.gFree.lock)
    	}
    }
    
  • ch08GC/termination: 写屏障状态描述不准确

    ch08GC/termination: 写屏障状态描述不准确

    实际描述

    • 文件路径:https://github.com/changkun/go-under-the-hood/blob/master/book/zh-cn/part2runtime/ch08GC/basic.md
    • 原文段落:
    标记终止 | 保证一个周期内标记任务完成,停止写屏障 | STW | 关闭
    

    预期描述

    标记终止 | 保证一个周期内标记任务完成,停止写屏障 | STW | 开启
    
  • 关于8.11 过去、现在与未来章节的疑问

    关于8.11 过去、现在与未来章节的疑问

    动机

    关于8.11 过去、现在与未来章节有点疑问

    需求说明

    内容中阐述 Go 1:朴素标记清扫 在 Go 1 的时代,尽管所有的用户代码都是并发执行的,但是一旦垃圾回收器开始进行垃圾回收工作时,所有的用户代码都会停止执行,而且垃圾回收器仅在一个线程上执行,这时是最原始的垃圾回收器的实现,即单线程版的三色标记清扫。

    Go 1.1, 1.3:并行清扫与精准标记 在 Go 1.3 时候,官方将三色标记清扫算法的垃圾回收代码改为并行,从而缩短了用户代码的停止时间,但是这仍然会造成大量的空隙,如果用户代码是一个 Web 应用,且正在处理一个非常重要的请求,则会对请求延迟造成巨大的影响。

    其中 Go 1 朴素标记清扫的时候,应该不算是三色标记清扫,但稳重最后一句话【即单线程版的三色标记清扫。】,是否应该修改为【即单线程版的标记清扫】。

    Go 1.1, 1.3:并行清扫与精准标记 其中【在 Go 1.3 时候,官方将三色标记清扫算法的垃圾回收代码改为并行】 是否应该修改为 【在 Go 1.3 时候,官方将标记清扫算法的垃圾回收代码改为并行】

  • ch6sched:stack.md 6.7.3 G 的创生,示例编译后的代码新版本有变化

    ch6sched:stack.md 6.7.3 G 的创生,示例编译后的代码新版本有变化

    问题描述

    欧神你好,在《6.7.3 G 的创生》一章中,示例代码在新版本编译后有一些变化。以下是我的理解:

    	0x001d 00029 (hello.go:8)	MOVL	$16, (SP) // 将 16 放到 SP 的位置,16 是第一个参数 siz,因为是 int32,所以是 MOVL。数字是 16 是因为有 string 和 string.len 两个参数加一起占 16 个字节
    	0x0024 00036 (hello.go:8)	LEAQ	"".hello·f(SB), AX // 将 hello 的调用地址传给 AX
    	0x002b 00043 (hello.go:8)	MOVQ	AX, 8(SP) // 将 hello 的调用地址放入 8(SP) 的位置
    	0x0030 00048 (hello.go:8)	LEAQ	go.string."hello world"(SB), AX // 将“hello world”放入 AX
    	0x0037 00055 (hello.go:8)	MOVQ	AX, 16(SP) // 将“hello world”放在 16(SP) 的位置
    	0x003c 00060 (hello.go:8)	MOVQ	$11, 24(SP) // 将 $11 放在 24(SP) 的位置,11 是 string 的长度,string 是结构体,结构体在传参中会扁平化为多个参数
    

    在新版本变化后,栈布局的图依然能帮助理解,但有些细节不一致了,在我看来现在的布局是这样的:

                 栈布局
    40(SP)+-----------------+      高地址
          |    caller BP    |       
    32(SP)+-----------------+ <-- main.BP
          |  11 string.len  |
    24(SP)+-----------------+ 
          |  "hello world"  |
    16(SP)+-----------------+ <-- fn + sys.PtrSize
          |      hello      |
    8(SP) +-----------------+ <-- fn
          |       siz       |
    (SP)  +-----------------+ <-- SP
          |    newproc PC   |  
          +-----------------+ callerpc: 要运行的 Goroutine 的 PC
          |                 |
          |                 |       低地址
    

    对比两个图,我有些疑惑: hello 函数地址现在占了 8 个字节,而不是原有图中的两个字节,是因为内存对齐的需求吗? “hello world” 现在是字符串完整的值传递,而不是地址传递,是有什么区别吗? “newproc PC” 是如何计算出的在 main.SP 下方的地址呢?我在生成的汇编代码中没有发现这里的处理。

    环境

    $ go version
    go version go1.15.5 darwin/amd64
    
  • 更新 sync.Map 基准测试

    更新 sync.Map 基准测试

    问题描述1

    原文中的"在两种情况下由于普通的 map+mutex",这句不知道是什么意思.

    sync.Map 宣称内部做了特殊的优化,在两种情况下由于普通的 map+mutex。
    

    https://github.com/golang-design/under-the-hood/blame/023270d971dc5c19e997e2277dfce4c172e92c67/book/zh-cn/part1basic/ch05sync/map.md#L8

    问题描述2

    这个性能对比图,不太明白纵坐标是什么意思。 在我的印象中,系统的sync.Map在读多写少的场景下是有优势的。 不知道这个图能不能体现出这个特性?

    map-syncmap

    在测试中,我们测试了:n 个 key 中,每个 key 产生 1 次写行为,每个 key 产生 n 次读行为。
    图1:map+sync.Mutex 、map+sync.RWMutex与 sync.Map 之间单次写多次读场景下的性能对比
    

    https://github.com/golang-design/under-the-hood/blame/023270d971dc5c19e997e2277dfce4c172e92c67/book/zh-cn/part1basic/ch05sync/map.md#L13-L16

  • ch06sched/preemption: go loop 抢占示例代码 sleep 时间延长

    ch06sched/preemption: go loop 抢占示例代码 sleep 时间延长

    实际描述

    • 文件路径:part2runtime/ch06sched/preemption
    • 原文段落:
    // 此程序在 Go 1.14 之前的版本不会输出 OK
    package main
    import (
    	"runtime"
    	"time"
    )
    func main() {
    	for i := 0; i < runtime.GOMAXPROCS(0); i++ {
    		go func() {
    			for {
    			}
    		}()
    	}
    	time.Sleep(time.Millisecond)
    	println("OK")
    }
    

    预期描述

    // 此程序在 Go 1.14 之前的版本不会输出 OK
    package main
    import (
    	"runtime"
    	"time"
    )
    func main() {
    	for i := 0; i < runtime.GOMAXPROCS(0); i++ {
    		go func() {
    			for {
    			}
    		}()
    	}
    	time.Sleep(time.Second)
    	println("OK")
    }
    

    附图

    image

  • ch06sched/exec: m0.g0 在 mstart 之前是否已经初始化完成?

    ch06sched/exec: m0.g0 在 mstart 之前是否已经初始化完成?

    问题描述

    你好,我在学习这一章时了解到会在 mstart 中确定 m.g0 的栈边界,但是我在 darwin 系统中使用 gdb 进行调试时发现,第一个进入 mstart 中的线程(应该是 m0 吧),osStack = false,也就是说 g0 已经初始化完成了,请问是在哪里完成的这一步呢?

    调试过程

    系统:darwin

    gdb b runtime.mstart
    

    然后单步调试往下走,第一个进入断点的线程,osStack 是为false的,之后创建的都是 true。 后面创建的 M 是使用系统栈的这个我理解,在 allocm 中有根据系统判断是否使用系统栈,darwin 在判断逻辑中,但是第一个线程为啥没用系统栈呢......

  • 第二部分运行时 第七章节 内存分配 部分是基于哪个branch或者tag的?

    第二部分运行时 第七章节 内存分配 部分是基于哪个branch或者tag的?

    问题描述

    第二部分运行时 第七章节 内存分配 部分是基于哪个branch或者tag的? 我看了golang 源码tag go 1.15没有7.4 大对象分配中的allocSpanLocked。 能否给出您描述的代码的commit id或者别的信息,有利于读者结合代码阅读。 请在此描述你的问题,提问前请参考提问的智慧

  • 关于mutex的疑问

    关于mutex的疑问

    问题描述

    在5.3 mutex一节中,开头有这么一句话

    在正常模式中,等待者按照 FIFO 的顺序排队获取锁

    我注意到这个是官方注释 在下文的unlockSlow方法中,正常唤醒逻辑的代码是runtime_Semrelease(&m.sema, false, 1),您的注释是

    唤醒一个阻塞的 goroutine,但不是唤醒第一个等待者

    这两个说法是不是冲突了?

  • Yuasa 屏障的图画得是不是有问题?

    Yuasa 屏障的图画得是不是有问题?

    C.ref2 -> A,那么 shade(*slot)也就是shade(A),应该是把A着色为灰色,为什么图中是把A置为白色、C置为灰色了?

    func YuasaWritePointer(slot *unsafe.Pointer, ptr unsafe.Pointer) {
        shade(*slot)
        *slot = ptr
    }
    
    • 原文段落:8.2写屏障技术,图3:使用Yuasa写屏障赋值器

    image

    ps:还是同一个图,这个地方地方的文字和图不匹配,应该是YuasaWritePointer(C.ref3, B)吧 image

  • ch02life:main.md  init 部分相关内容已过时

    ch02life:main.md init 部分相关内容已过时

    动机

    init 相关内容已过时

    需求说明

    runtime.main 中关于 init 调用部分有更新,包括编译器部分。 相关 commit:https://github.com/golang/go/commit/d949d0b9252be1fffeadd65183a6bab3acf3de7a 本人水平有限,勉强能看懂一些,但还远远达不到能提 PR 修改此处。期待相关更新。

    ps.这个项目能重启真是太好了 🎉

Zero - If Google Drive says that 1 is under copyright, 0 must be under copyleft

zero Zero under copyleft license Google Drive's copyright detector says that fil

May 16, 2022
dockin ops is a project used to handle the exec request for kubernetes under supervision
dockin ops is a project used to handle the exec request for kubernetes under supervision

Dockin Ops - Dockin Operation service English | 中文 Dockin operation and maintenance management system is a safe operation and maintenance management s

Aug 12, 2022
A Kubernetes Native Batch System (Project under CNCF)
A Kubernetes Native Batch System (Project under CNCF)

Volcano is a batch system built on Kubernetes. It provides a suite of mechanisms that are commonly required by many classes of batch & elastic workloa

Jan 9, 2023
Kubernetes Native Edge Computing Framework (project under CNCF)
Kubernetes Native Edge Computing Framework (project under CNCF)

KubeEdge KubeEdge is built upon Kubernetes and extends native containerized application orchestration and device management to hosts at the Edge. It c

Jan 1, 2023
A super easy file encryption utility written in go and under 800kb
A super easy file encryption utility written in go and under 800kb

filecrypt A super easy to use file encryption utility written in golang ⚠ Help Wanted on porting filecrypt to other programing languages NOTE: if you

Nov 10, 2022
A Telegram Repo For Bots Under Maintenance Which Gives Faster Response To Users
A Telegram Repo For Bots Under Maintenance Which Gives Faster Response To Users

Maintenance Bot A Telegram Repo For Bots Under Maintenance Which Gives Faster Response To Users Requests » Report a Bug | Request Feature Table of Con

Mar 21, 2022
A distributed key value store in under 1000 lines. Used in production at comma.ai

minikeyvalue Fed up with the complexity of distributed filesystems? minikeyvalue is a ~1000 line distributed key value store, with support for replica

Jan 9, 2023
A tool to bring existing Azure resources under Terraform's management

Azure Terrafy A tool to bring your existing Azure resources under the management of Terraform. Install go install github.com/magodo/aztfy@latest Usage

Dec 9, 2021
OpenYurt - Extending your native Kubernetes to edge(project under CNCF)
OpenYurt - Extending your native Kubernetes to edge(project under CNCF)

openyurtio/openyurt English | 简体中文 What is NEW! Latest Release: September 26th, 2021. OpenYurt v0.5.0. Please check the CHANGELOG for details. First R

Jan 7, 2023
A tool to bring existing Azure resources under Terraform's management

Azure Terrafy A tool to bring your existing Azure resources under the management of Terraform. Goal Azure Terrafy imports the resources inside a resou

Jan 1, 2023
A distributed key value store in under 1000 lines. Used in production at comma.ai

minikeyvalue Fed up with the complexity of distributed filesystems? minikeyvalue is a ~1000 line distributed key value store, with support for replica

Jan 9, 2023
A sample web API in GO (with GIn) under a domain driven architecture.

Golang Sample API Domain Driven Design Pattern 1. About This sample project presents a custom made domain driven API architecture in Golang using the

Jan 10, 2022
A High Performance Object Storage released under Apache License
A High Performance Object Storage released under Apache License

MinIO Quickstart Guide MinIO is a High Performance Object Storage released under Apache License v2.0. It is API compatible with Amazon S3 cloud storag

Sep 30, 2021
ICPP 2022 (pap351) Under Review

SciCoFK: A Decentralized Scientific Computing Framework Embraced with Consensus and Incentive Mechanism This repo is the primary code of SciCoFK. In t

May 27, 2022
Deploy your own temporary email service with web interface under 15 minutes.

watt Watt is an open-source smtp wrapper written in Go that provides a simple web interface for creating and managing temporary email addresses. It is

May 7, 2023