脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - 深入Golang的接口interface

深入Golang的接口interface

2022-10-14 11:48​谈笑风生间 Golang

这篇文章主要介绍了深入Golang的接口interface,go不要求类型显示地声明实现了哪个接口,只要实现了相关的方法即可,编译器就能检测到,接下来关于接口interface的相关介绍需要的朋友可以参考下面文章内容

前言

go不要求类型显示地声明实现了哪个接口,只要实现了相关的方法即可,编译器就能检测到

空接口类型可以接收任意类型的数据:

?
1
2
3
4
5
6
7
type eface struct {
    // _type 指向接口的动态类型元数据
    // 描述了实体类型、包括内存对齐方式、大小等
    _type *_type
    // data 指向接口的动态值
    data  unsafe.Pointer
}

空接口在赋值时,_type 和 data 都是nil。赋值后,_type 会指向赋值的数据元类型,data 会指向该值

非空接口是有方法列表的接口类型,一个变量要赋值给非空接口,就要实现该接口里的所有方法

?
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
type iface struct {
    // tab 接口表指针,指向一个itab实体,存储方法列表和接口动态类型信息
    tab  *itab
    // data 指向接口的动态值(一般是指向堆内存的)
    data unsafe.Pointer
}
// layout of Itab known to compilers
// allocated in non-garbage-collected memory
// Needs to be in sync with
// ../cmd/compile/internal/gc/reflect.go:/^func.dumptabs.
type itab struct {
    // inter 指向interface的类型元数据,描述了接口的类型
    inter *interfacetype
    // _type 描述了实体类型、包括内存对齐方式、大小等
    _type *_type
    // hash 从动态类型元数据中拷贝的hash值,用于快速判断类型是否相等
    hash  uint32 // copy of _type.hash. Used for type switches.
    _     [4]byte
    // fun 记录动态类型实现的那些接口方法的地址
    // 存储的是第一个方法的函数指针
    // 这些方法是按照函数名称的字典序进行排列的
    fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
 
// interfacetype 接口的类型元数据
type interfacetype struct {
    typ     _type
    // 记录定义了接口的包名
    pkgpath name
    // mhdr 标识接口所定义的函数列表
    mhdr    []imethod
}

深入Golang的接口interface

itab是可复用的,go会将itab缓存起来,构造一个hash表用于查出和查询缓存信息<接口类型, 动态类型>

如果itab缓存中有,可以直接拿来使用,如果没有,则新创建一个itab,并放入缓存中

一个Iface中的具体类型中实现的方法会被拷贝到Itab的fun数组中

?
1
2
3
4
5
6
7
8
9
10
11
// Note: change the formula in the mallocgc call in itabAdd if you change these fields.
type itabTableType struct {
    size    uintptr             // length of entries array. Always a power of 2.
    count   uintptr             // current number of filled entries.
    entries [itabInitSize]*itab // really [size] large
}
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
    // compiler has provided some good hash codes for us.
    // 用接口类型hash和动态类型hash进行异或
    return uintptr(inter.typ.hash ^ typ.hash)
}

接口类型和nil作比较

接口值的零值指接口动态类型和动态值都为nil,当且仅当此时接口值==nil

如何打印出接口的动态类型和值?

定义一个iface结构体,用两个指针来描述itab和data,然后将具体遍历在内存中的内容强行解释为我们定义的iface

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type iface struct{
    itab, data uintptr
}
func main() {
    var a interface{} = nil
    var b interface{} = (*int)(nil)
    x := 5
    var c interface{} = (*int)(&x)
    ia := *(*iface)(unsafe.Pointer(&a))
    ib := *(*iface)(unsafe.Pointer(&b))
    ic := *(*iface)(unsafe.Pointer(&c))
    fmt.Println(ia, ib, ic)
    fmt.Println(*(*int)(unsafe.Pointer(ic.data)))
}
// 输出
// {0 0} {17426912 0} {17426912 842350714568}
// 5

检测类型是否实现了接口:

赋值语句会发生隐式的类型转换,在转换过程中,编译器会检测等号右边的类型是否实现了等号左边接口所规定的函数

?
1
2
3
4
// 检查 *myWriter 类型是否实现了 io.Writer 接口
var _ io.Writer = (*myWriter)(nil)
// 检查 myWriter 类型是否实现了 io.Writer 接口
var _ io.Writer = myWriter{}

接口转换的原理

将一个接口转换为另一个接口:

  • 实际上是调用了convI2I
  • 如果目标是空接口或和目标接口的inter一样就直接返回
  • 否则去获取itab,然后将值data赋入,再返回
?
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// 接口间的赋值实际上是调用了runtime.convI2I
// 实际上是要找到新interface的tab和data
// convI2I returns the new itab to be used for the destination value
// when converting a value with itab src to the dst interface.
func convI2I(dst *interfacetype, src *itab) *itab {
    if src == nil {
        return nil
    }
    if src.inter == dst {
        return src
    }
    return getitab(dst, src._type, false)
}
 
// 关键函数,获取itab
// getitab根据interfacetype和_type去全局的itab哈希表中查找,如果找到了直接返回
// 否则根据inter和typ新生成一个itab,插入到全局itab哈希表中
//
// 查找了两次,第二次上锁了,目的是可能会写入hash表,阻塞其余协程的第二次查找
func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
    // 函数列表为空
    if len(inter.mhdr) == 0 {
        throw("internal error - misuse of itab")
    }
 
    // easy case
    if typ.tflag&tflagUncommon == 0 {
        if canfail {
            return nil
        }
        name := inter.typ.nameOff(inter.mhdr[0].name)
        panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
    }
 
    var m *itab
 
    // First, look in the existing table to see if we can find the itab we need.
    // This is by far the most common case, so do it without locks.
    // Use atomic to ensure we see any previous writes done by the thread
    // that updates the itabTable field (with atomic.Storep in itabAdd).
    // 使用原子性去保证我们能看见在该线程之前的任意写操作
    // 确保更新全局hash表的字段
    t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
    // 遍历一次,找到了就返回
    if m = t.find(inter, typ); m != nil {
        goto finish
    }
    // 没找到就上锁,再试一次
    // Not found.  Grab the lock and try again.
    lock(&itabLock)
    if m = itabTable.find(inter, typ); m != nil {
        unlock(&itabLock)
        goto finish
    }
    // hash表中没找到itab就新生成一个itab
    // Entry doesn't exist yet. Make a new entry & add it.
    m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize, 0, &memstats.other_sys))
    m.inter = inter
    m._type = typ
    // The hash is used in type switches. However, compiler statically generates itab's
    // for all interface/type pairs used in switches (which are added to itabTable
    // in itabsinit). The dynamically-generated itab's never participate in type switches,
    // and thus the hash is irrelevant.
    // Note: m.hash is _not_ the hash used for the runtime itabTable hash table.
    m.hash = 0
    m.init()
    // 加到全局的hash表中
    itabAdd(m)
    unlock(&itabLock)
finish:
    if m.fun[0] != 0 {
        return m
    }
    if canfail {
        return nil
    }
    // this can only happen if the conversion
    // was already done once using the , ok form
    // and we have a cached negative result.
    // The cached result doesn't record which
    // interface function was missing, so initialize
    // the itab again to get the missing function name.
    panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}
 
// 查找全局的hash表,有没有itab
// find finds the given interface/type pair in t.
// Returns nil if the given interface/type pair isn't present.
func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
    // Implemented using quadratic probing.
    // Probe sequence is h(i) = h0 + i*(i+1)/2 mod 2^k.
    // We're guaranteed to hit all table entries using this probe sequence.
    mask := t.size - 1
    // 根据inter,typ算出hash值
    h := itabHashFunc(inter, typ) & mask
    for i := uintptr(1); ; i++ {
        p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize))
        // Use atomic read here so if we see m != nil, we also see
        // the initializations of the fields of m.
        // m := *p
        m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
        if m == nil {
            return nil
        }
        // inter和typ指针都相同
        if m.inter == inter && m._type == typ {
            return m
        }
        h += i
        h &= mask
    }
}
// 核心函数。填充itab
// 检查_type是否符合interface_type并且创建对应的itab结构体将其放到hash表中
// init fills in the m.fun array with all the code pointers for
// the m.inter/m._type pair. If the type does not implement the interface,
// it sets m.fun[0] to 0 and returns the name of an interface function that is missing.
// It is ok to call this multiple times on the same m, even concurrently.
func (m *itab) init() string {
    inter := m.inter
    typ := m._type
    x := typ.uncommon()
 
    // both inter and typ have method sorted by name,
    // and interface names are unique,
    // so can iterate over both in lock step;
    // the loop is O(ni+nt) not O(ni*nt).
    // inter和typ的方法都按方法名称进行排序
    // 并且方法名是唯一的,因此循环次数的固定的
    // 复杂度为O(ni+nt),而不是O(ni*nt)
    ni := len(inter.mhdr)
    nt := int(x.mcount)
    xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]
    j := 0
    methods := (*[1 << 16]unsafe.Pointer)(unsafe.Pointer(&m.fun[0]))[:ni:ni]
    var fun0 unsafe.Pointer
imethods:
    for k := 0; k < ni; k++ {
        i := &inter.mhdr[k]
        itype := inter.typ.typeOff(i.ityp)
        name := inter.typ.nameOff(i.name)
        iname := name.name()
        ipkg := name.pkgPath()
        if ipkg == "" {
            ipkg = inter.pkgpath.name()
        }
        // 第二层循环是从上一次遍历到的位置开始的
        for ; j < nt; j++ {
            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 {
                        // 获取函数地址,放入itab.func数组中
                        ifn := typ.textOff(t.ifn)
                        if k == 0 {
                            fun0 = ifn // we'll set m.fun[0] at the end
                        } else {
                            methods[k] = ifn
                        }
                    }
                    continue imethods
                }
            }
        }
        // didn't find method
        m.fun[0] = 0
        return iname
    }
    m.fun[0] = uintptr(fun0)
    return ""
}
 
// 检查是否需要扩容,并调用方法将itab存入
// itabAdd adds the given itab to the itab hash table.
// itabLock must be held.
func itabAdd(m *itab) {
    // Bugs can lead to calling this while mallocing is set,
    // typically because this is called while panicing.
    // Crash reliably, rather than only when we need to grow
    // the hash table.
    if getg().m.mallocing != 0 {
        throw("malloc deadlock")
    }
 
    t := itabTable
    // 检查是否需要扩容
    if t.count >= 3*(t.size/4) { // 75% load factor
        // Grow hash table.
        // t2 = new(itabTableType) + some additional entries
        // We lie and tell malloc we want pointer-free memory because
        // all the pointed-to values are not in the heap.
        t2 := (*itabTableType)(mallocgc((2+2*t.size)*goarch.PtrSize, nil, true))
        t2.size = t.size * 2
 
        // Copy over entries.
        // Note: while copying, other threads may look for an itab and
        // fail to find it. That's ok, they will then try to get the itab lock
        // and as a consequence wait until this copying is complete.
        iterate_itabs(t2.add)
        if t2.count != t.count {
            throw("mismatched count during itab table copy")
        }
        // Publish new hash table. Use an atomic write: see comment in getitab.
        atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
        // Adopt the new table as our own.
        t = itabTable
        // Note: the old table can be GC'ed here.
    }
    // 核心函数
    t.add(m)
}
 
// 核心函数
// add adds the given itab to itab table t.
// itabLock must be held.
func (t *itabTableType) add(m *itab) {
    // See comment in find about the probe sequence.
    // Insert new itab in the first empty spot in the probe sequence.
    // 在探针序列第一个空白点插入itab
    mask := t.size - 1
    // 计算hash值
    h := itabHashFunc(m.inter, m._type) & mask
    for i := uintptr(1); ; i++ {
        p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize))
        m2 := *p
        if m2 == m {
            // itab已被插入
            // A given itab may be used in more than one module
            // and thanks to the way global symbol resolution works, the
            // pointed-to itab may already have been inserted into the
            // global 'hash'.
            return
        }
        if m2 == nil {
            // Use atomic write here so if a reader sees m, it also
            // sees the correctly initialized fields of m.
            // NoWB is ok because m is not in heap memory.
            // *p = m
            // 使用原子操作确保其余的goroutine下次查找的时候可以看到他
            atomic.StorepNoWB(unsafe.Pointer(p), unsafe.Pointer(m))
            t.count++
            return
        }
        h += i
        h &= mask
    }
}
 
// hash函数,编译期间提供较好的hash codes
func itabHashFunc(inter *interfacetype, typ *_type) uintptr {
    // compiler has provided some good hash codes for us.
    return uintptr(inter.typ.hash ^ typ.hash)
}

具体类型转空接口时,_type 字段直接复制源类型的 _type;调用 mallocgc 获得一块新内存,把值复制进去,data 再指向这块新内存。

具体类型转非空接口时,入参 tab 是编译器在编译阶段预先生成好的,新接口 tab 字段直接指向入参 tab 指向的 itab;调用 mallocgc 获得一块新内存,把值复制进去,data 再指向这块新内存。

而对于接口转接口,itab 调用 getitab 函数获取。只用生成一次,之后直接从 hash 表中获取。

实现多态

  • 多态的特点
  • 一种类型具有多种类型的能力
  • 允许不同的对象对同一消息做出灵活的反应
  • 以一种通用的方式对待多个使用的对象
  • 非动态语言必须通过继承和接口的方式实现

实现函数的内部,接口绑定了实体类型,会直接调用fun里保存的函数,类似于s.tab->fun[0],而fun数组中保存的是实体类型实现的函数,当函数传入不同实体类型时,实际上调用的是不同的函数实现,从而实现了多态

到此这篇关于深入Golang的接口interface的文章就介绍到这了,更多相关Go 接口 interface内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://juejin.cn/post/7101676463509733412

延伸 · 阅读

精彩推荐
  • Golang利用dep代替go get获取私有库的方法教程

    利用dep代替go get获取私有库的方法教程

    go get 从指定源上面下载或者更新指定的代码和依赖,并对他们进行编译和安装,但go get功能比较差,所以下面这篇文章主要给大家介绍了关于利用dep代替...

    金庆5242020-05-10
  • GolangGo使用sync.Map来解决map的并发操作问题

    Go使用sync.Map来解决map的并发操作问题

    在 Golang 中 map 不是并发安全的,sync.Map 的引入确实解决了 map 的并发安全问题,本文就详细的介绍一下如何使用,感兴趣的可以了解一下...

    新亮笔记4512021-11-26
  • Golang使用GO实现Paxos共识算法的方法

    使用GO实现Paxos共识算法的方法

    这篇文章主要介绍了使用GO实现Paxos共识算法,本文给大家介绍的非常详细,对大家的学习或工作,具有一定的参考借鉴价值,需要的朋友可以参考下...

    苹果没有熟3872020-09-19
  • Golanggo语言中的interface使用实例

    go语言中的interface使用实例

    这篇文章主要介绍了go语言中的interface使用实例,go语言中的interface是一组未实现的方法的集合,如果某个对象实现了接口中的所有方法,那么此对象就实现了此...

    Golang教程网5232020-04-26
  • GolangGo语言的GOPATH与工作目录详解

    Go语言的GOPATH与工作目录详解

    这篇文章主要介绍了Go语言的GOPATH与工作目录详解,本文详细讲解了GOPATH设置、应用目录结构、编译应用等内容,需要的朋友可以参考下 ...

    junjie18042020-04-08
  • Golanggolang 各种排序大比拼实例

    golang 各种排序大比拼实例

    这篇文章主要介绍了golang 各种排序大比拼实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    Mr_len4022021-03-20
  • GolangGo结合Redis用最简单的方式实现分布式锁

    Go结合Redis用最简单的方式实现分布式锁

    本文主要介绍了Go结合Redis用最简单的方式实现分布式锁示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    jiaxwu4302022-08-31
  • Golang浅析Go语言编程当中映射和方法的基本使用

    浅析Go语言编程当中映射和方法的基本使用

    这篇文章主要介绍了浅析Go语言编程当中映射和方法的基本使用,是golang入门学习中的基础知识,需要的朋友可以参考下 ...

    脚本之家2642020-04-28