概述
作者简介:C/C++ 、Golang 领域耕耘者,创作者
个人主页:作者主页
专栏地址:go语言专栏
算法题专栏:刷题专栏
如果感觉博主的文章还不错的话,还请关注➕ 、点赞???? 、收藏????三连支持一下博主哦~~~
文章目录
- ???? cgo 如何调用及背后原理
- 1.1 linux 平台调用cgo
- 1.2 调用cgo 场景
- 1.3 做法
- 1.4 分析cgo 背后作用
- 1.5 调度器的配合
- 1.6 协程栈的切换
- 1.7 cgo 的优缺点
- 1.8 小结
- ???? defer 底层作用原理分析
- 2.1 defer 在项目中常见写法
- 2.2 通过命令看defer 背后执行的逻辑
- 2.3 defer 思路
- 2.4 实现1: 堆上分配
- 2.5 实现2: 栈上分配1.13 之后出现
- 2.6 实现3: 开放编码
- 2.7 小结
- ???? recover 如何在panic 中拯救程序
- 3.1 panic 的基本使用
- 3.2 panic + defer
- 3.3 recover
- 3.4 panic + defer + recover原理
- 3.5 小结
- ???? 反射是怎么实现的
- 4.1 需求
- 4.2 元数据
- 4.3 对象的类型
- 4.4 对象的值
- 4.5 对象到反射对象
- 4.6 反射的实现演练
- 4.7 小结
本节主要讲解cgo、defer、recover、反射相关知识点
???? cgo 如何调用及背后原理
1.1 linux 平台调用cgo
因为cgo 需要用到gcc,而gcc 在windows平台上支持不是很好,这里直接在linux 平台演示
1.2 调用cgo 场景
假设在go中加一个c代码的注释,图中的sum函数
假如在项目中有一个很复杂的c代码,在go中不好复现,现在需要用go去调用它
1.3 做法
- 注释需要在main 包下面
- import “C” 需要紧挨这注释代码
在goland中出现下图这样的颜色说明调用就正确了
1.4 分析cgo 背后作用
go tool cgo main.go
(用命令看cgo底层做了什么)运行上面命令之后就生成了一个obj文件夹
在main.cgo1.go
文件中,是这样的代码
cgo_gotypes.go
文件中
通过上面两个文件,根据func_sum
函数可知,跳转到main.cgo2.c
文件中
在上面就是c代码,在c中申请了两个入参,一个出参, 上面的代码就是用的c语言中的栈(这里就跳出go中的内存管理了)
原理步骤归纳:
- 在内存中开辟一个结构体
- 结构体中含有参数和返回值
- 结构体地址传入C方法
- C方法将结果写入返回值的位置
最终的过程也需要runtime
中的一些配合(也就是go中的配合)
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
实际上,执行了runtime包里面的函数
2.3 defer 思路
实际上,在go官方源码中,defer 在功能上有两种思路
- 协程记录defer 信息,函数退出时调用
- 将defer 代码直接编译进函数尾(当函数里面业务量少的时候)
2.4 实现1: 堆上分配
1.12 之前使用
在堆上开辟一个sched.deferpool
,遇到defer
语句,将信息放入deferpool
,函数返回时,从deferpoll
取出执行(deferpoll
数据结构在p结构体中)
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 带崩溃了
在go 的源码中,有这样一段代码:
在panic.go
中,如果有recover的话,就不会带崩上一级协程
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
- 反射对象到对象时,根据类型和地址还原数据
- 通过反射调用方法,可以将框架和用户方法解耦
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习
如果觉得对你有帮助的话:
???? 点赞,你的认可是我创作的动力!
???? 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!
最后
以上就是贤惠吐司为你收集整理的Go语言的其他高级特性的全部内容,希望文章能够帮你解决Go语言的其他高级特性所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复