Go知识点

一些GO的知识点。

Go的知识点

编译相关

Go可以生成汇编,采用下列指令进行查看:

1
go build -gcflags -S main.go

Go可以生成汇编优化过程。通过GOSSAFUNC,可以生成html文件来交互查看。

SSA:静态单赋值。即每个变量只会被赋值一次,便于优化。同一变量名字会生成不同后缀来区分。

GO可以生成各种机器码,包括wasm。

TODO:待到学完编译原理在看。

数据结构

数组

只有类型和大小完全相同,才能认为是同一数据类型,可以用==比较。

【…】T{1,2,3}与【3】T{1,2,3}使用上完全相同,只是语法糖。

当元素数量小于或者等于 4 个时,会直接将数组中的元素放置在栈上。

当元素数量大于 4 个时,会将数组中的元素放到静态存储区初始化然后拷贝到栈上。

简单的越界错误可以由编译器发现,复杂的则需要运行时。运行时发现越界时候会panicIndex和runtime.goPanicIndex。运行时会插入代码,检查边界,越界则panic,通过则Load或Store。

切片

slice有data指针,len长度,cap容量。

切片初始化可以有make创建,字面量初始化,或者截取数组或者切片的一部分。

切片逃逸:1、如果函数外部没有引用,则优先放到栈中;2、如果函数外部存在引用,则必定放到堆中;

切片很小并且非发生逃逸,会在栈或静态存储区初始化。否则在堆区初始化。

cap足够时候append覆盖时候会优化情况。不够则会调用growslice扩容并且拷贝过去。扩容规则如下:

如果期望容量大于当前容量的两倍就会使用期望容量; 如果当前切片的长度小于 1024 就会将容量翻倍; 如果当前切片的长度大于 1024 就会每次增加 25% 的容量,直到新容量大于期望容量;

大致容量如此,接下来还需要进行对齐操作。

拷贝时候会memove,这比依次拷贝性能更好,耗费资源仍然较多。

哈希表

开放地址法:填空。

拉链法:挂上链表。

map底层为hmap,由若干个bmap桶组成。每个桶通常可以存8个元素。

通过哈希值低8位找到桶,在通过高八位对比tophash数组来查找。

分配时候若桶满则可以放入溢出桶,用overflow指针指过去。
元素少于25个则直接hash,多余25则创建两个数组存储键值,然后for哈希过去。

在make时候,则会检查内存是否足够,获取哈希种子,计算最少桶数量,创建保存桶数组。

仅获取value的访问采用mapaccess1,会计算哈希值,查找桶,查找tophash,并且访问。mapaccess2就多个返回值。

当装载因子超过6.5或使用太多溢出桶会触发扩容 。扩容将桶数量翻倍,通过growWork触发,访问时候访问旧桶,写入则分流过到新桶。

删除和赋值逻辑类似。

字符串

只读字符串,由data指针和len组成。

如果要修改,则要转换成为【】byte来进行。开销并不小。

逃逸分析

取地址或者逃出了函数,按照此原则分析变量分配方法

语言基础

函数调用

在c语言中,函数调用参数使用寄存器与栈实现。6个一下为寄存器。以上放入栈。

Go采用栈来入参和出参,简化了实现,不必考虑架构。

Go无论是基本类型,结构体,还是指针,都会传值拷贝。

接口

接口引入中间层,实现上下游解耦。在GO中,实现接口所有方法就算实现了接口。

interface{}不是任意类型。当我们转换成interface时候,类型就是interface。

函数接收者不是指针的时候,能通过结构体和指针使用方法。是指针的时候,只能通过指针使用方法。不能同时存在同名方法定义两次。

nil在转换成interface类型时候会包含以前的类型信息,因此转换后不是nil。

对于空interface而言,结构体为eface,不含方法,之包含类型与数据指针。_type类型中有数据大小,哈希值来快速确定类型是否相当,equal判断多个对象是否相等。

非空interface包含itab指针与数据指针。itab中包含对上述_type的哈希,来确认目标类型与具体类型是否相等。有fun动态大小数组,当成虚函数表,存储函数指针。同时包含原类型type和接口类型interfacetype。

非指针变量转换为接口会拷贝到堆上。

断言switch a.(type)时候会检查哈希值。

反射

TypeOf接受空接口参数,转换为emptyInterface类型,并且获取typ并返回。

ValueOf则是先将其逃逸到堆上,热爱华南虎使用unpackEface将传进来的转换为emptyInterface结构体,包装成Value结构体并且返回。

更新变量时候,会检查变量是否对外公开。然后调用assignTo返回新反射对象,并且覆盖原始反射变量。

eface用于运行时,emptyInterface用于反射。

for range

在for _,v:=range arr时候,系统会创建一个变量v,将数组值在迭代中覆盖过去,这导致v的地址在循环中不变。

使用for循环清空数组,切片,哈希表时候,会编译成arrayClear,加速过程,开销并不大。

range循环不会永动机,因为最开始会调用len来获取终止条件。

哈希表遍历:随机选一个正常桶开始,遍历桶内,桶外溢出桶,再按顺序来。

select

select与switch类似,但状态只能是chan收发状态。

多个条件都满足时候,switch会随机执行,避免饥饿状态。

select的case用结构体表示

select不存在case直接阻塞,只有一个case则变成单if,一个case一个default会改写成if,else,默认情况下会将case转换为结构体,调用selectgo函数获取一个可行的结构体,使用一连串if观察是哪个被选中了。

轮询会随机开始,加锁顺序则是按地址排序。

调用select先确认轮询顺序加锁顺序,之后如果能立即执行则立即执行并返回,不能则创建sudog结构体,加入相关的收发队列,挂起等唤醒,唤醒了则遍历去找。

defer

defer会在函数结束执行,而不是代码作用域结束。

defer参数的值是在调用时候计算的。

defer采用延迟链表,最早只有堆上分配,后期有栈上分配,开放编码优化。

根据defer数量和return数量判断是否开放编码优化。直接插入到函数返回前。

panic recover

panic时候,会放到panic链表最前面,然后获取defer链表,执行,最后panic。

程序的恢复由gopanic执行。取出栈顶指针和pc,执行recovery函数,recovery则跳回去。

make new

make需要判断类型,new则只用初始化指针,申请空间。

context

基本用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func main() {
ctx, cancel := context.WithCancel(context.Background())
go watch(ctx, "【监控1】")
go watch(ctx, "【监控2】")
go watch(ctx, "【监控3】")

time.Sleep(10 * time.Second)
fmt.Println("可以了,通知监控停止")
cancel()
//为了检测监控过是否停止,如果没有监控输出,就表示停止了
time.Sleep(5 * time.Second)
}

func watch(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name, "监控退出,停止了...")
return
default:
fmt.Println(name, "goroutine监控中...")
time.Sleep(2 * time.Second)
}
}
}

chan

chan结构体内包含队列元素个数,循环队列长度,数据指针,发送waitq,接收waitq。
waitq表示双向链表,包含向前的sudog和向后的sudog。

发送前会先上锁,再判断是否closed。
直接发送:先拷贝到目标地址,再标记接收goroutine,放进处理器runnext等待执行。
带缓冲区:计算下一个可以存储数据的地方,再将数据拷贝到缓冲区,增加索引与计时器。
缓冲区为循环数组。
阻塞式发送:则先获取发送数据的goroutine,再获取sudog设置阻塞发送相关信息,加入发送等待队列,沉睡并等待唤醒,唤醒后首尾。

接收:如果不在缓冲区且就绪,则直接拷贝。在缓冲区,则拷贝并且移动缓冲区指针。
chan为空则挂起,关闭则检查缓冲区,无数据则返回。如果发送队列存在挂起的,则从缓冲区拷贝到接收,再将发送挂起的拷贝进缓冲区。缓冲区存在数据则直接读,否则挂起。

GMP模型

G表示goroutine,M表示线程,P表示处理器。
G类似线程,包含自己的内存,栈,寄存器状态,再调度器保存或者恢复上下文的时候用到。
M操作系统线程,GOMAXPROCS控制活跃数量,默认为CPU核数,减少系统调度开销。
P提供上下文环境,可以调度线程上等待队列。
调度时间点:主动挂起,系统调用,协作式调度,系统监控。

垃圾收集

三色标记法:黑色,白色与灰色。

  • 黑色代表活跃的对象,已经被访问过,并且本对象引用的其他对象也被标记过。
  • 灰色代表活跃的对象,已经被访问,但存在引用的其他对象未被访问。
  • 白色代表潜在的垃圾,未被访问过。

当开始运行时候,根对象被标记为灰色对象,然后之后的程序只从灰色对象向外扩展。类似BFS。当灰色集合不存在的时候,遍历结束,回收白色的垃圾。
GC是一个过程,中途可能改变对象的引用指向,可能会增加,可能会删除,因此需要遵守两个不变性之一:强三色不变性-黑色不会指向白色;弱三色不变性-黑色指向的白色的可达路径上一定有灰色(白色肯定能被灰色扩展到)
引入写屏障技术,在写进内存前做一些干涉,保证gc的正确性即可。存在两种写屏障:Dijkstra插入写屏障与Yuasa删除写屏障。
Dijkstra插入写屏障:在黑色需要指向白色时候,将白色染成灰色即可。但由于栈上的对象会被认为成根对象,因此要么为栈上的对象添加写屏障,要么在标记结束后再次扫描栈。
Yuasa删除写屏障:在删除对象的时候,将被删除的对象标记为灰色。这样保证了弱三色不变性,但回收精度低,有的需要下一轮才能回收。

Author

王钦砚

Posted on

2020-12-02

Licensed under

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×