go-源码研读-interface
代码版本:1.12.7
interface
是Go
语言里面的接口,可以理解为一种方法声明的集合约定,整个ducktyping
就是通过这个完成的。
- 任何类型实现了在
interface
接口中声明的全部方法,则表明该类型实现了该接口,可以实现多个interface
interface
可以作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值。interface
中的方法不能重载,比如test()
和test(a int)
不能同时存在
interface 常见有两种用法,如下:
1 | var a interface{} // 一个空的interface变量,go的“弱类型” 底层其实是eface |
go的两种用法映射到底层是两种不同的实现,eface和iface 。 eface是没有函数的interface(go的“弱类型”),iface内部存在函数(go的接口)。
弱类型实现基本上都是 desc + data
eface结构
1 | type eface struct { |
变量整体结构
变量的整体结构如下:
其中_type 和 uncommonType为通用数据结构, selfType对于不同类型来说有不同的实现。以struct举例说明:
对于struct的_type指向的是structType结构的_type字段,对于一个struct的描述,增加了后面的内容,由structType和uncommonType两个结构组成。 其中uncommonType是与函数相关,在不存在函数的情况下,没有该字段, 即eface没有uncommonType字段。
源码如下
1 | type eface struct { |
思考:
golang定义一个struct类型,底层是否头部自动插入_type类型?
golang的变量使用和定义是分离,即使用还是一样的;定义的部分则保存在二进制文件中。
基础数据类型(int32,float)是否有_type定义?
是有的,反编译后可以看到
对于一个struct类型变量转interface 会发生什么?
在编译阶段完成:
- struct type的定义在二进制文件
- 生成对应的类型赋值的二进制文件
- 从.rodata文件中读取该struct的_type信息。
- 构建该struct的eface结构,与该变量关联。
golang 的底层selfType到底有多少种呢?
一共有以下这几个:interfacetype、maptype、arraytype、chantype、slicetype、functype、ptrtype、structtype
_type源码
1 | const ( |
iface
go语言中用来描述接口的底层结构。
常见使用场景:
1 | type IGreeting interface { |
底层结构
1 | type iface struct { |
go如何判断类型实现了该接口
遍历接口的方法,判断该类型是否实现了该方法。如果全部都是实现了,即继承了该接口,否则就是没有继承该接口。
源码:
1 | // 填充m.fun数组,如果类型(拿struct举例)你未实现接口,则将m.fun[0]置为0 |
接口的全局存储表
程序启动的时候,会加载所有的接口类型,这些内容会保存在一个全局的哈希表中。
全局哈希表结构:1
2
3
4
5type itabTableType struct {
size uintptr // 全局哈希表的表大小
count uintptr // 填充的大小
entries [itabInitSize]*itab // 数组(哈希表)
}
什么时候执行存储逻辑?
在程序启动时会调用schedinit函数。在schedinit函数内执行一系列初始化逻辑,其中就有itabsinit函数。
1 | func itabsinit() { // 该方法调用是在schedinit中,即程序启动时 |
全局哈希表的添加操作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
26
27
28
29
30
31
32
33
34
35
36
37func itabAdd(m *itab) { // 向全局的哈希表中添加接口,
// ... 省略一部分代码
t := itabTable // 全局的itabTable哈希表
if t.count >= 3*(t.size/4) { // 75% load factor 容量超过3/4,扩容
t2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))
t2.size = t.size * 2
iterate_itabs(t2.add) // rehash迁移
if t2.count != t.count {
throw("mismatched count during itab table copy")
}
atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2)) // itabTable指向扩容后哈希表
t = itabTable
}
t.add(m) // 执行添加逻辑
}
func (t *itabTableType) add(m *itab) { // 添加逻辑
mask := t.size - 1
h := itabHashFunc(m.inter, m._type) & mask // 哈希后的值
for i := uintptr(1); ; i++ {
p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize)) // 得到下表对应的位置
m2 := *p
if m2 == m { // 已经存在返回
return
}
if m2 == nil { // 位置为空存储
atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
t.count++
return
}
h += i // 哈希表解决哈希冲突的一种
h &= mask
}
}
全局哈希表的查找操作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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
// ... 省略一部分代码
var m *itab
t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable))) // 全局哈希表
if m = t.find(inter, typ); m != nil { // 全局哈希表中查找
goto finish
}
lock(&itabLock)
if m = itabTable.find(inter, typ); m != nil { // 获取锁再次查找(最新的)
unlock(&itabLock)
goto finish
}
// 没找到的情况下,新建itab,执行init函数(见上面init函数,填充itab.fun数组),将新建的添加到全局哈希表
m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
m.inter = inter
m._type = typ
m.init()
itabAdd(m)
unlock(&itabLock)
finish:
if m.fun[0] != 0 {
return m
}
if canfail { // 允许失败,返回空。即m.fun[0] == 0, 未实现接口
return nil
}
// 不允许失败,panic
panic(&TypeAssertionError{concreteString: typ.string(), assertedString: inter.typ.string(), missingMethod: m.init()})
}
// 正常的hash获取逻辑
func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
mask := t.size - 1
h := itabHashFunc(inter, typ) & mask
for i := uintptr(1); ; i++ {
p := (**itab)(add(unsafe.Pointer(&t.entries), h*sys.PtrSize))
m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
if m == nil {
return nil
}
if m.inter == inter && m._type == typ {
return m
}
h += i
h &= mask
}
}