Golang 中学到的新东西
数据类型
string 类型
string 类型使用 2 个 word(64 bit 系统为 8 byte * 2)表示:一个 word 是指针,指向字符串存储区域;一个 word 表示长度数据。
slice $\leftrightarrow$ unsafe.Pointer
| |
| |
or
| |
or
| |
map 实现
整个页面的内容对我来说都是新的:https://tiancaiamao.gitbooks.io/go-internals/content/zh/02.3.html 不过这个页面描述的内容和最新的 Golang source 有一定差别。
读 HashMap 的实现,里面的一些核心关键词:bucket、overflow 让我理解起来有些困难。查询 HashMap 相关的一些资料后有了进一步了解。
bucket一般使用某种array管理,从key经过hash-function映射的hash-value(可能截取一部分,也可以视作 sub-hash,我自己编的)作为index直接得到。一个bucket中可能包含多个不同的hash-value,它们截取那一部分得到的index相同。因此bucket会用一个数据结构管理这些冲突的值,可能是linked-list或者tree-map之类的。这些内部的数据结构中的node存储着真正对应map的key\value对(pair)。- 如果
bucket太满了,比如元素的数量超过bucket数量一定倍数(load factor),则会进行扩容,所有元素都被rehashed到一个新的值。 - 采用这种方式实现
HashMap,bucket可以有两种选择:
- Direct chaining 只存一个指向冲突元素集合的
header - Seperate Chaining 在
bucket存一部分(一个)元素集合(GolangHashMap实现里放了 8 个),和一个指向剩下冲突元素集合的header
- 上面
header指向的元素集合叫overflow list或者overflow some-other-data-structure
有了这些背景后,看代码应该会比较清晰了。
目前 Golang 中的 bucket 是为 insert 操作优化的,找到第一个空余位置就可以插入,但是删除的时候要把所有相同 key 的元素都删掉,要遍历 bucket 的 overflow 集合。
如果 key 或者 value 小于 128 字节,那么它们是直接在 bucket 存储值,否则存指向数据的指针。
nil 语义
按照 Golang 规范,任何类型在未初始化时都对应一个零值:
bool$\rightarrow$trueinteger$\rightarrow$0string$\rightarrow$""pointer/function/interface/slice/channel/map$\rightarrow$nil
关于 interface{}
| |
关于 channel
一些操作规则:
- 读写一个
nil的channel会立即阻塞 - 读一个关闭的
channel会立刻返回一个channel元素类型的零值,即chan int会返回0 - 写一个关闭的
channel会导致panic
函数调用
汇编
可以看一下这个 Golang 的官方介绍页面:https://golang.org/doc/asm
add.go
| |
add_amd64.s 或使用其他平台后缀,和 add.go 在同一个目录
| |
Golang 调用 C
add.c ,和 add.go 在同一个目录
| |
编译这个包:go install add
C 文件中需要包含 runtime.h 头文件。因为 Golang 使用特殊寄存器存放像全局 struct G 和 struct M ,包含这个文件可以让所有链接到 Go 的 C 文件感知这一点,避免编译器使用这些特定的寄存器做其他用途。
上面示例中返回值为空,使用 ret 作为返回值,FLUSH 在 pkg/runtime/runtime.h 中定义为 USED() ,防止编译器优化掉对某个变量的赋值操作(因为看不到这个变量在后面其他地方使用了)。
函数调用时的内存布局
Golang 中使用的 C 编译器是 plan9 的 C 编译器,与 gcc 有一定差异。 这个页面中有部分基础介绍: https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.1.html
如果返回多个值,func f(a, b int) (d, e int) 内存布局如下所示:
slot for e
slot for d
b
a
<- SP
调用后为
slot for e
slot for d
b
a <- FP
PC <- SP
f's stack
plan9 的 C 汇编器对被调用函数的参数值的修改是会返回到调用函数中的。
| |
go 关键字
f(1, 2, 3) 的汇编:
| |
go f(1, 2, 3) 的汇编:
| |
12 是参数占用的大小,runtime.newproc 函数接受的参数为:参数大小、新的 goroutine 要运行的函数、函数的参数。runtime.newproc 会新建一个栈空间,将栈参数的 12 个字节复制到新的栈空间,并让栈指针指向参数。可以看做 runtime.newproc(size, f, args) 。
defer 关键字
return x 不是原子语句,函数执行顺序为:
- 给返回值赋值
defer调用return
defer 实现对应 runtime.deferproc,其出现的地方插入指令 call runtime.deferproc ,函数返回之前的地方,插入 call runtime.deferreturn 。 goroutine 的控制结构中有一张表记录 defer,表以栈行为运作。
Continuous Stack
我也基本理解了思路,具体细节可以看:https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.5.html
最后的 runtime.lessstack 有些没看懂。
闭包
闭包中引用的变量不能在栈上分配,否则闭包函数返回的时候,栈上变量的地址就失效了。
逃逸分析(escape analyze)
Golang 有个特性,可以自动识别哪些变量在栈上分配,哪些在堆上分配。
| |
| |
在编译的过程中可以通过指令输出哪些变量逃逸了:go build --gcflags=-m main.go
闭包结构体
闭包将函数和它引用的环境表示为一个结构体:
| |
整体思路是返回闭包的时候,返回一个结构体,包含闭包返回函数的地址和引用的环境中的变量地址。
| |
| |