深入理解 Golang: defer、recover、panic、reflect

cgo

在 Go 中调用 C 代码:

/*
int sum(int a, int b) {
  return a+b;
}
*/
import "C"

func main() {
    println(C.sum(1, 1))
}
  • cgo 是让 go 语言调用 C 方法的技术
  • cgo 需要 go 调度器和协程栈的配合
  • cgo 一般用于找不到 go 实现的情况
  • cgo 不能提高性能,是一个临时解决方案

defer

defer:函数退出前一定会执行 defer 后的语句。

思路:

  1. 协程记录 defer 信息,函数退出时调用
  2. 将 defer 代码直接编译进函数尾

1.1 堆上分配

  • 1.12 之前使用
  • 在堆上开辟一个 sched.deferpool
  • 遇到 defer 语句,将信息放入 deferpool
  • 函数返回时,从 deferpool 取出执行

deferpool 记录在线程结构体 p 上:

type p struct {
    // ...
    deferpool    []*_defer // pool of available defer structs (see panic.go)
    deferpoolbuf [32]*_defer
    // ...
}

1.2 栈上分配

  • 1.13 之后出现
  • 遇到 defer 语句,将信息放到栈上
  • 函数返回时,从栈中取出执行
  • 只能保存一个 defer 信息

2.1 开放编码

  • 1.14 之后出现
  • 如果 defer 语句在编译时就可以确定,则直接改写用户代码,defer 语句放入函数末尾
func main() {
    defer fmt.Println("defer1")
    defer fmt.Println("defer2")
    fmt.Println("main")
}

recover、panic

panic

  • panic 会抛出错误
  • 终止协程运行
  • 带崩整个 Go 程序
func main() {
    go func() {
        panic("panic")
        fmt.Println("do")
    }()
    fmt.Println("main")
    time.Sleep(1 * time.Second)
    /*
    main
    panic: panic
    */
}

panic + defer

  • panic 在退出协程之前会执行本协程所有已注册的 defer
  • 不会执行其他协程的 defer
func main() {
    defer fmt.Println("defer1")

    go func() {
        defer fmt.Println("defer2")
        panic("panic1")
        fmt.Println("do")
    }()

    fmt.Println("main")
    time.Sleep(1 * time.Second)

    /**
    main
    defer2
    panic: panic1
    */
}

panic + defer + recover

在 defer 中执行 recover 可以拯救协程

  • 如果涉及到 recover,defer 会使用堆上分配
  • 遇到 panic,panic 会从 deferpool 中取出 defer 语句并执行
  • defer 中调用 recover,可以终止 panic 的过程
func main() {
    defer fmt.Println("defer1")

    go func(){
         defer func() {
            if r := recover(); r != nil {
                fmt.Println("Recovered. Error: ", r)
            }
        }()
        panic("panic1")
        fmt.Println("do")
    }()
    fmt.Println("main")

    time.Sleep(1 * time.Second)

    /**
    main
    Recovered. Error:  panic1
    defer1
    */
}

反射 reflect

func main() {
 s := "moody"

 stype := reflect.TypeOf(s)
 fmt.Println("TypeOf s:", stype)

 svalue := reflect.ValueOf(s)
 fmt.Println("ValueOf s:", svalue)
}

反射对象到对象

元数据

  • 元数据就是 “数据的数据”
  • 把对象的类型表示成一个数据类型
  • 把对象的值表示成一个数据类型
func main() {
    s := "go"

    // 对象的类型
    // 把对象的类型表示成一个接口
    stype := reflect.TypeOf(s)
    fmt.Println("TypeOf s:", stype) // TypeOf s: string

    // 对象的值
    // 把对象的值表示成一个接口
    svalue := reflect.ValueOf(s)
    fmt.Println("ValueOf s:", svalue) // ValueOf s: go

    s2 := svalue.Interface().(string)

    // 
    fmt.Println("s2:", s2) // s2: go
}
  • runtime.eface 是运行时对空接口的表示
  • runtime.emptyinterface 是 reflect 包对空接口的表示
  • 对象到反射对象时,编译器会入参提前转换为 eface
  • 反射对对象到对象时,根据地址和类型还原数据

反射调用方法

通过反射调用方法,实现用户方法和框架解耦

func MyAdd(a, b int) int { return a + b }

func CallAdd(f func(a int, b int) int) {
    v := reflect.ValueOf(f)
    if v.Kind() != reflect.Func {
        return
    }
    argv := make([]reflect.Value, 2)
    argv[0] = reflect.ValueOf(1)
    argv[1] = reflect.ValueOf(1)

    result := v.Call(argv)
    fmt.Println(result[0].Int())
}

func main() {
    CallAdd(MyAdd)
}

你可能感兴趣的:(Golang,golang,开发语言,后端)