我是靠谱客的博主 贤惠吐司,最近开发中收集的这篇文章主要介绍Go语言的其他高级特性,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述


作者简介: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 做法

  1. 注释需要在main 包下面
  2. 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中的内存管理了)

原理步骤归纳:

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

最终的过程也需要runtime 中的一些配合(也就是go中的配合)

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
在这里插入图片描述
在这里插入图片描述
实际上,执行了runtime包里面的函数

2.3 defer 思路

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

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

2.4 实现1: 堆上分配

1.12 之前使用
在堆上开辟一个sched.deferpool ,遇到defer 语句,将信息放入deferpool,函数返回时,从deferpoll取出执行(deferpoll数据结构在p结构体中)

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 带崩溃了

在go 的源码中,有这样一段代码:
在这里插入图片描述
panic.go 中,如果有recover的话,就不会带崩上一级协程

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. 通过反射调用方法,可以将框架和用户方法解耦

推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

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

最后

以上就是贤惠吐司为你收集整理的Go语言的其他高级特性的全部内容,希望文章能够帮你解决Go语言的其他高级特性所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部