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$true
integer
$\rightarrow$0
string
$\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
闭包结构体
闭包将函数和它引用的环境表示为一个结构体:
|
|
整体思路是返回闭包的时候,返回一个结构体,包含闭包返回函数的地址和引用的环境中的变量地址。
|
|
|
|