代码版本:1.12.7

interfaceGo语言里面的接口,可以理解为一种方法声明的集合约定,整个ducktyping就是通过这个完成的。

  1. 任何类型实现了在interface 接口中声明的全部方法,则表明该类型实现了该接口,可以实现多个interface
  2. interface可以作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值。
  3. interface中的方法不能重载,比如test()test(a int)不能同时存在

interface 常见有两种用法,如下:

1
2
3
4
var a interface{}   // 一个空的interface变量,go的“弱类型” 底层其实是eface
type A interface { // 一个存在函数的interface类型,go的接口 底层其实是iface
Print()
}

go的两种用法映射到底层是两种不同的实现,eface和iface 。 eface是没有函数的interface(go的“弱类型”),iface内部存在函数(go的接口)。

弱类型实现基本上都是 desc + data

eface结构

1
2
3
4
type eface struct {
_type *_type // desc
data unsafe.Pointer // data
}

变量整体结构

变量的整体结构如下:

其中_type 和 uncommonType为通用数据结构, selfType对于不同类型来说有不同的实现。以struct举例说明:

对于struct的_type指向的是structType结构的_type字段,对于一个struct的描述,增加了后面的内容,由structType和uncommonType两个结构组成。 其中uncommonType是与函数相关,在不存在函数的情况下,没有该字段, 即eface没有uncommonType字段。

源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type eface struct {
_type *_type // desc
data unsafe.Pointer // data
}

type structtype struct {
typ _type
pkgPath name
fields []structfield
}

type uncommontype struct {
pkgpath nameOff
mcount uint16 // number of methods
_ uint16 // unused
moff uint32 // offset from this uncommontype to [mcount]method
_ uint32 // unused
}

思考:

  1. golang定义一个struct类型,底层是否头部自动插入_type类型?

    golang的变量使用和定义是分离,即使用还是一样的;定义的部分则保存在二进制文件中。

  2. 基础数据类型(int32,float)是否有_type定义?

    是有的,反编译后可以看到

  3. 对于一个struct类型变量转interface 会发生什么?

    在编译阶段完成:

    1. struct type的定义在二进制文件
    2. 生成对应的类型赋值的二进制文件
      1. 从.rodata文件中读取该struct的_type信息。
      2. 构建该struct的eface结构,与该变量关联。
  4. golang 的底层selfType到底有多少种呢?

    一共有以下这几个:interfacetype、maptype、arraytype、chantype、slicetype、functype、ptrtype、structtype

_type源码

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
51
52
53
54
55
56
const (
tflagUncommon tflag = 1 << 0 // 标记是否有uncommon内容(即:记录pkgpath和方法的内容),目前看:只有匿名结构体和reflect动态创建的struct并且没有methods时,该值为0
/*
type AA struct {}
var a = AA{}
b := &a
fmt.Println(reflect.TypeOf(a),reflect.TypeOf(b))
a的name为:"main.AA"
b的name为:"*main.AA"

在底层golang为了做优化,让a和b的name都指向了"*main.AA"。然后通过tflagExtraStar区分。
*/
tflagExtraStar tflag = 1 << 1 //标记name是否是多含*,如果为true,实际的名称是:name[1:]。
tflagNamed tflag = 1 << 2 // 标记是否有类型名称(必须通过type定义的类型才有类型名称,没有定义名字的类型,比如函数,匿名结构体,类型的指针)。
)

type rtype struct { // _type类型
size uintptr // 类型占用字节大小
/*
type AA struct {
a *int
b int
c *int
d *int
e int
f int
}
size = 48
ptrdata = 32
*/
ptrdata uintptr // 类型前ptrdata字节包含指针
hash uint32 // 类型的哈希(其实就是类型名称(str)的哈希)
tflag tflag // 看上面tflagUncommon,tflagExtraStar,tflagNamed注释
/*
align和fieldAlign含义分别为:
align:申请内存时结构体内部类型内存对齐大小
fieldAlign:作为其他结构体时结构体内部变量内存对齐大小
以上为翻译注释以及结合代码前后提交历史,从现在源码来看这两个值完全一样,没有任何区别
*/
align uint8
fieldAlign uint8
kind uint8 // 基础类型的枚举值
alg *typeAlg // 见下方typeAlg
gcdata *byte // gc相关:标记结构体类型中哪些字节是指针类型(mask数组),与ptrdata配合使用。
str nameOff // 类型名称
/*
例子:
struct AA {} // AA的rtype里面的ptrToThis 这里ptrToThis其实是*AA(*AA也是一种类型)的偏移
*/
ptrToThis typeOff // *type类型的定义偏移。一般的代码中(除reflect外)定义的类型都会在编译时,生成两种type(一个是type类型,一个是*type类型),并存在二进制文件rodata块中。运行时会直接使用。
}

type typeAlg struct {
hash func(unsafe.Pointer, uintptr) uintptr // 类型对应的哈希函数
equal func(unsafe.Pointer, unsafe.Pointer) bool // 类型对应的比较函数
}

iface

go语言中用来描述接口的底层结构。
常见使用场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type IGreeting interface {
sayHello()
}

type Go struct{}
func (g Go) sayHello() {
fmt.Println("Hi, I am GO!")
}

type PHP struct{}
func (p PHP) sayHello() {
fmt.Println("Hi, I am PHP!")
}

func main() {
var c, d IGreeting = Go{}, PHP{}
c.sayHello()
d.sayHello()
}

底层结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type iface struct {
tab *itab // 接口实例的类型信息
data unsafe.Pointer // 接口实例数据的指针
}

type itab struct {
inter *interfacetype // 接口类型信息
_type *_type // struct类型信息
hash uint32 // 哈希函数
_ [4]byte
fun [1]uintptr // 标记位。如果为0表示未实现接口的所有方法。 不为0 表示函数方法数组的首指针。
}

type interfacetype struct {
typ _type // 接口类型描述 _type
pkgpath name // 接口路径
mhdr []imethod // 接口方法数组
}

go如何判断类型实现了该接口

遍历接口的方法,判断该类型是否实现了该方法。如果全部都是实现了,即继承了该接口,否则就是没有继承该接口。

源码:

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
// 填充m.fun数组,如果类型(拿struct举例)你未实现接口,则将m.fun[0]置为0
func (m *itab) init() string {
inter := m.inter // 接口描述
typ := m._type // struct描述
x := typ.uncommon() // struct的uncommon字段,目的是取struct方法数量以及方法指针

ni := len(inter.mhdr) // 接口方法数量
nt := int(x.mcount) // struct方法数量
xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt] // struct方法数组
j := 0
imethods:
// 这里看似双重遍历,由于方法数组都是有序的,所以其实时间复杂度为ni+nt, 而不是ni*nt
for k := 0; k < ni; k++ { // 遍历接口方法。
i := &inter.mhdr[k] // 接口中的一个方法i
itype := inter.typ.typeOff(i.ityp) // i方法描述
name := inter.typ.nameOff(i.name) // 方法的name字段
iname := name.name() // name转化为string
ipkg := name.pkgPath() // 路径
if ipkg == "" {
ipkg = inter.pkgpath.name()
}
for ; j < nt; j++ { // 遍历struct的方法
t := &xmhdr[j]
tname := typ.nameOff(t.name)
if typ.typeOff(t.mtyp) == itype && tname.name() == iname { //判等
pkgPath := tname.pkgPath()
if pkgPath == "" {
pkgPath = typ.nameOff(x.pkgpath).name()
}
if tname.isExported() || pkgPath == ipkg {
if m != nil {
ifn := typ.textOff(t.ifn)
*(*unsafe.Pointer)(add(unsafe.Pointer(&m.fun[0]), uintptr(k)*sys.PtrSize)) = ifn
}
continue imethods // 存在i方法就continue到外层for
}
}
}
m.fun[0] = 0 // 没有找到i方法, 将m.fun[0] 置为0
return iname
}
m.hash = typ.hash
return ""
}

接口的全局存储表

程序启动的时候,会加载所有的接口类型,这些内容会保存在一个全局的哈希表中。

全局哈希表结构:

1
2
3
4
5
type itabTableType struct {
size uintptr // 全局哈希表的表大小
count uintptr // 填充的大小
entries [itabInitSize]*itab // 数组(哈希表)
}

什么时候执行存储逻辑?
在程序启动时会调用schedinit函数。在schedinit函数内执行一系列初始化逻辑,其中就有itabsinit函数。

1
2
3
4
5
6
7
8
9
func itabsinit() {                        // 该方法调用是在schedinit中,即程序启动时
lock(&itabLock)
for _, md := range activeModules() { // 遍历所有模块,将所有模块下的接口类型添加在全局哈希表中
for _, i := range md.itablinks { // 遍历该模块下的所有接口类型
itabAdd(i) // 添加到全局哈希表
}
}
unlock(&itabLock)
}

全局哈希表的添加操作

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
func 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
51
func 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
}
}