本节主要讲解cgo、defer、recover、反射相关知识点

💜 cgo 如何调用及背后原理

1.1 linux 平台调用cgo

因为cgo 需要用到gcc,而gcc 在windows平台上支持不是很好,这里直接在linux 平台演示

1.2 调用cgo 场景

sum函数

1.3 做法

  1. 注释需要在main 包下面
  2. import “C” 需要紧挨这注释代码

在goland中出现下图这样的颜色说明调用就正确了
在这里插入图片描述

1.4 分析cgo 背后作用

go tool cgo main.gomain.cgo1.gocgo_gotypes.gofunc_summain.cgo2.c

原理步骤归纳:

  1. 在内存中开辟一个结构体
  2. 结构体中含有参数和返回值
  3. 结构体地址传入C方法
  4. C方法将结果写入返回值的位置
runtime

1.5 调度器的配合

从前面的文章可知,在go中M(系统级线程)和G(go的协程)的关系如图:
在这里插入图片描述
那调度器在cgo程序过程中做了什么呢,大致做了以下工作:

  1. 协程需要抢占式调度
  2. 进入C程序之后,调度器无法抢占协程
  3. 调度器停止对此协程的调度

1.6 协程栈的切换

在go中调用cgo程序时,当中涉及到了协程栈的切换,切换后,有以下两点需要知道

  1. C的栈不受Runtime 管理
  2. 进入C时,需要将当前栈切换到线程的系统栈上

1.7 cgo 的优缺点

  1. cgo 可以让go调用现成的C实现
  2. cgo 限制了go语言的跨平台特性(很多c程序不跨平台)
  3. cgo并不能提高Go语言的性能

1.8 小结

  1. cgo 是让go语言调用C方法的技术
  2. cgo 需要go调度器和协程栈的配合
  3. cgo 一般用于找不到go实现的情况
  4. cgo 不能提高性能,是一个临时解决方案

🧡 defer 底层作用原理分析

2.1 defer 在项目中常见写法

在这里插入图片描述

2.2 通过命令看defer 背后执行的逻辑

go build -gcflags -S main.go

2.3 defer 思路

实际上,在go官方源码中,defer 在功能上有两种思路

  1. 协程记录defer 信息,函数退出时调用
  2. 将defer 代码直接编译进函数尾(当函数里面业务量少的时候)

2.4 实现1: 堆上分配

sched.deferpooldeferdeferpooldeferpolldeferpoll

2.5 实现2: 栈上分配1.13 之后出现

  1. 遇到defer 语句,将信息放入栈上
  2. 函数返回时,从栈中取出执行
  3. 只能保存一个defer(虽然没有垃圾回收这个缺点,但栈上分配多个话,可能会导致栈的扩容,所以规定只保存一个)

2.6 实现3: 开放编码

上面两个思路其实都是差不多的(都是上面思路中第一种方法)
1.14 之后出现

  1. 如果defer 语句在编译时就可以固定
  2. 直接改写用户代码,defer 语句放入函数末尾

2.7 小结

  1. defer可以方便业务的编写
  2. defer 有两种思路,三种实现
  3. 性能最好的是开放编码法(但是不好触发)

💚 recover 如何在panic 中拯救程序

3.1 panic 的基本使用

在这里插入图片描述
panic 之后,不光当前的协程会退,而且会带崩启动它的协程,最后会退到这个主协程崩。所以两个打印都不会打印

panic 在程序中的调用流程:

  1. panic 会抛出错误
  2. 终止协程运行
  3. 带崩整个Go程序

3.2 panic + defer

在这里插入图片描述
执行结果:在这里插入图片描述
程序解释:

  1. panic 在退出协程之前会执行所有已注册的defer
  2. 不会执行其他协程的defer (在panic.go 中gopanic 函数中)

3.3 recover

在这里插入图片描述
如果在上述代码中增加红色框框中的代码,主协程就不会被触发的panic 带崩溃了

panic.go

3.4 panic + defer + recover原理

  1. 在defer 中执行recover,可以拯救panic 的协程

原理:

  1. 如果涉及recover, defer 会使用堆上分配(deferpool)
  2. 遇到panic, panic 会从deferpoll取出的defer 语句,执行
  3. defer 中调用recover,可以终止panic 的过程

3.5 小结

  1. panic 可能带崩整个Go程序
  2. panic 在退出协程之前会执行所有已注册的defer
  3. 在defer 中执行recover,可以拯救panic 的过程

💙 反射是怎么实现的

4.1 需求

  1. 获取对象的类型
  2. 对任意类型变量赋值
  3. 调用任意方法

4.2 元数据

  1. 元数据就是“数据的数据”
  2. 把对象的类型表示成一个数据类型
  3. 把对象的值表示成一个数据类型

4.3 对象的类型

  1. 接口 reflect.Type
  2. 把对象的类型表示成一个接口
  3. 就能对类型做各种操作

4.4 对象的值

  1. 结构体 reflect.Value
  2. 把对象的值表示成一个结构体
  3. 就能对值做各种操作

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)
}
  1. 通过反射调用方法,可以将框架和用户方法解耦
  2. 往往需要用户注册方法,框架调用
  3. 很多框架的HTTP调用处理使用此思路

4.7 小结

  1. runtime.eface 是运行时对空接口的表示
  2. reflect.emptyInterface 是reflect 包对空接口表示
  3. 对象到反射对象时,编译器会将入参提前转为eface
  4. 反射对象到对象时,根据类型和地址还原数据
  5. 通过反射调用方法,可以将框架和用户方法解耦

如果觉得对你有帮助的话:
👍 点赞,你的认可是我创作的动力!
🧡 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!