本节主要讲解cgo、defer、recover、反射相关知识点
💜 cgo 如何调用及背后原理
1.1 linux 平台调用cgo
因为cgo 需要用到gcc,而gcc 在windows平台上支持不是很好,这里直接在linux 平台演示
1.2 调用cgo 场景
sum函数
1.3 做法
- 注释需要在main 包下面
- import “C” 需要紧挨这注释代码
在goland中出现下图这样的颜色说明调用就正确了
1.4 分析cgo 背后作用
go tool cgo main.gomain.cgo1.gocgo_gotypes.gofunc_summain.cgo2.c
原理步骤归纳:
- 在内存中开辟一个结构体
- 结构体中含有参数和返回值
- 结构体地址传入C方法
- C方法将结果写入返回值的位置
runtime
1.5 调度器的配合
从前面的文章可知,在go中M(系统级线程)和G(go的协程)的关系如图:
那调度器在cgo程序过程中做了什么呢,大致做了以下工作:
- 协程需要抢占式调度
- 进入C程序之后,调度器无法抢占协程
- 调度器停止对此协程的调度
1.6 协程栈的切换
在go中调用cgo程序时,当中涉及到了协程栈的切换,切换后,有以下两点需要知道
- C的栈不受Runtime 管理
- 进入C时,需要将当前栈切换到线程的系统栈上
1.7 cgo 的优缺点
- cgo 可以让go调用现成的C实现
- cgo 限制了go语言的跨平台特性(很多c程序不跨平台)
- cgo并不能提高Go语言的性能
1.8 小结
- cgo 是让go语言调用C方法的技术
- cgo 需要go调度器和协程栈的配合
- cgo 一般用于找不到go实现的情况
- cgo 不能提高性能,是一个临时解决方案
🧡 defer 底层作用原理分析
2.1 defer 在项目中常见写法
2.2 通过命令看defer 背后执行的逻辑
go build -gcflags -S main.go
2.3 defer 思路
实际上,在go官方源码中,defer 在功能上有两种思路
- 协程记录defer 信息,函数退出时调用
- 将defer 代码直接编译进函数尾(当函数里面业务量少的时候)
2.4 实现1: 堆上分配
sched.deferpooldeferdeferpooldeferpolldeferpoll
2.5 实现2: 栈上分配1.13 之后出现
- 遇到defer 语句,将信息放入栈上
- 函数返回时,从栈中取出执行
- 只能保存一个defer(虽然没有垃圾回收这个缺点,但栈上分配多个话,可能会导致栈的扩容,所以规定只保存一个)
2.6 实现3: 开放编码
上面两个思路其实都是差不多的(都是上面思路中第一种方法)
1.14 之后出现
- 如果defer 语句在编译时就可以固定
- 直接改写用户代码,defer 语句放入函数末尾
2.7 小结
- defer可以方便业务的编写
- defer 有两种思路,三种实现
- 性能最好的是开放编码法(但是不好触发)
💚 recover 如何在panic 中拯救程序
3.1 panic 的基本使用
panic 之后,不光当前的协程会退,而且会带崩启动它的协程,最后会退到这个主协程崩。所以两个打印都不会打印
panic 在程序中的调用流程:
- panic 会抛出错误
- 终止协程运行
- 带崩整个Go程序
3.2 panic + defer
执行结果:
程序解释:
- panic 在退出协程之前会执行所有已注册的defer
- 不会执行其他协程的defer (在panic.go 中gopanic 函数中)
3.3 recover
如果在上述代码中增加红色框框中的代码,主协程就不会被触发的panic 带崩溃了
panic.go
3.4 panic + defer + recover原理
- 在defer 中执行recover,可以拯救panic 的协程
原理:
- 如果涉及recover, defer 会使用堆上分配(deferpool)
- 遇到panic, panic 会从deferpoll取出的defer 语句,执行
- defer 中调用recover,可以终止panic 的过程
3.5 小结
- panic 可能带崩整个Go程序
- panic 在退出协程之前会执行所有已注册的defer
- 在defer 中执行recover,可以拯救panic 的过程
💙 反射是怎么实现的
4.1 需求
- 获取对象的类型
- 对任意类型变量赋值
- 调用任意方法
4.2 元数据
- 元数据就是“数据的数据”
- 把对象的类型表示成一个数据类型
- 把对象的值表示成一个数据类型
4.3 对象的类型
- 接口 reflect.Type
- 把对象的类型表示成一个接口
- 就能对类型做各种操作
4.4 对象的值
- 结构体 reflect.Value
- 把对象的值表示成一个结构体
- 就能对值做各种操作
4.5 对象到反射对象
见代码:
4.6 反射的实现演练
场景1: 在println 中就用到了反射判断类型在打印出来的
场景2: 见代码
package main
import (
"fmt"
"reflect"
)
func Add(a int, b int) int {
return a + b
}
func Sub(a int, b int) int {
return a - b
}
func CallFunc(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(2)
re := v.Call(argv)
fmt.Println(re[0].Int())
}
func main() {
CallFunc(Add)
CallFunc(Sub)
}
- 通过反射调用方法,可以将框架和用户方法解耦
- 往往需要用户注册方法,框架调用
- 很多框架的HTTP调用处理使用此思路
4.7 小结
- runtime.eface 是运行时对空接口的表示
- reflect.emptyInterface 是reflect 包对空接口表示
- 对象到反射对象时,编译器会将入参提前转为eface
- 反射对象到对象时,根据类型和地址还原数据
- 通过反射调用方法,可以将框架和用户方法解耦
如果觉得对你有帮助的话:
👍 点赞,你的认可是我创作的动力!
🧡 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!