defer
延时性
不管defer函数后的执行路径如何,最终都将被执行。在Go语言中,defer一般被用于资源的释放及异常panic的处理。保证函数在任意路径执行结束后都能够关闭资源。defer是一种优雅的关闭资源的方式,能减少大量冗余的代码并避免由于忘记释放资源而产生的错误
保证函数在任意路径执行结束后都能够关闭资源。defer是一种优雅的关闭资源的方式,能减少大量冗余的代码并避免由于忘记释放资源而产生的错误
defer执行顺序为后进先出LIFO(入栈出栈)
func executPanic() {
defer func() {
if errMsg := recover(); errMsg != nil {
fmt.Println(errMsg)
}
}()
panic("This is Panic situation")
fmt.Println("func executes completely")
}
func main() {
executePanic()
fmt.Println("Main block is executed completetly..")
}
参数预计算
参数的预计算指当函数到达defer语句时,延迟调用的参数将立即求值,传递到defer函数中的参数将预先被固定,而不会等到函数执行完成后再传递参数到defer中。 会立即传递
func main() {
a := 1
defer func(b int) {
fmt.Println("defer b", b)
}(a + 1)
a = 99
}
// 函数打印 defer 2
返回值陷阱
defer还有一种非常容易犯错的场景,涉及与返回值参数结合。
【如果defer中对返回值做了修改】
return其实并不是一个原子操作,其包含了下面几步:将返回值保存在栈上→执行defer函数→函数返回。 遵循这个原则,就不怕出错
Go 的 panic 和 recover 的真相
原文:https://zhuanlan.zhihu.com/p/418257257
写的挺好
interface
Go是强类型语言,各个实例变量的类型信息正是存放在interface{}中的,Go中的反射也与其底层结构有关。
iface
和 eface
都是 Go 中描述interface{}的底层结构体,区别在于 iface
描述的接口包含方法,而 eface
则是不包含任何方法的空接口:interface{}
。
eface
eface
比较简单,只维护了 _type
字段,表示空接口所承载的具体的实体类型,以及data
描述了具体的值。
type eface struct {
_type *_type
data unsafe.Pointer
}
data
字段是iface
和 eface
都有的结构,这个是一个内存指针,指向interface{}实例对象信息的存储地址,在这里,我们可以获取对象的具体属性的数值信息。
而interface{}的类型信息是存放在_type
结构体中的,如下所示,在eface
中,直接存放了_type
的指针,iface
中多了一层封装,本节我们主要针对eface
做梳理,所以介绍_type
结构体。
type _type struct {
// 类型大小
size uintptr
ptrdata uintptr
// 类型的 hash 值
hash uint32
// 类型的 flag,和反射相关
tflag tflag
// 内存对齐相关
align uint8
fieldalign uint8
// 类型的编号,有bool, slice, struct 等等等等
kind uint8
alg *typeAlg
// gc 相关
gcdata *byte
str nameOff
ptrToThis typeOff
}
iface
与eface
不同,iface
结构体中要同时储存方法信息,其数据结构如下图所示。正如前面所说的,itab
结构体封装了_type
结构体,同样利用_type
储存类型信息,另外,其还有一些其他的属性。hash
是对_type
结构体中hash
的拷贝,提高类型断言的效率。bad
与inhash
都是标记位,提高gc以及其他活动的效率。fun
指向方法信息的具体地址。
另外,interfacetype
,他描述的是接口静态类型信息。
fun
字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。这里只会列出实体类型和接口相关的方法,实体类型的其他方法并不会出现在这里。
Go 通过 itab
中的 fun
字段来实现接口变量调用实体类型的函数。C++ 中的虚函数表是在编译期生成的;而 Go 的 itab
中的 fun
字段是在运行期间动态生成的。原因在于,Go 中实体类型可能会无意中实现 N 多接口,很多接口并不是本来需要的,所以不能为类型实现的所有接口都生成一个 itab
, 这也是“非侵入式”带来的影响;这在 C++ 中是不存在的,因为派生需要显示声明它继承自哪个基类。
type iface struct {
tab *itab
data unsafe.Pointer
}
type itab struct {
inter *interfacetype
_type *_type
link *itab
hash uint32 // copy of _type.hash. Used for type switches.
bad bool // type does not implement interface
inhash bool // has this itab been added to hash?
unused [2]byte
fun [1]uintptr // variable sized
}
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
重要结构关系图
接口转化
通过前面提到的 iface
的源码可以看到,实际上它包含接口的类型 interfacetype
和 实体类型的类型 _type
,这两者都是 iface
的字段 itab
的成员。也就是说生成一个 itab
同时需要接口的类型和实体的类型。
当判定一种类型是否满足某个接口时,Go 使用类型的方法集和接口所需要的方法集进行匹配,如果类型的方法集完全包含接口的方法集,则可认为该类型实现了该接口。
例如某类型有 m
个方法,某接口有 n
个方法,则很容易知道这种判定的时间复杂度为 O(mn)
,Go 会对方法集的函数按照函数名的字典序进行排序,所以实际的时间复杂度为 O(m+n)
。
Go的接口实现是非侵入式的,而是鸭子模式:如果某个东西长得像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那它就可以被看成是一只鸭子。duck typing