golang defer,func()闭包,panic ,recover,contex

defer的底层逻辑

1 defer链表

在这里插入图片描述
每一个gotoutine在runtime.g结构体中都包含了*defer指针字段,指向defer链表的头,而defer链表是从头部插入(先进后出)
在这里插入图片描述
1deferproc在编译的时候注册defer函数,
2函数返回之前,通过returndefer执行注册的defer函数,再return(先注册–返回前执行)

2 defer结构体

在这里插入图片描述
defer结构体

3 defer函数注册/函数栈帧

在这里插入图片描述
1栈帧先是函数A的两个局部变量a,b
2然后是A1的参数a=1,deferproc注册的fn地址以及参数siz
在这里插入图片描述

3 deferproc在堆上注册结构体,参数a=1(若函数有返回值的话还有返回值),link和panic为nil,调用者fn=addr2,返回地址addr,调用者栈指针sp of A, started为false,参数占8字节,这个结构体会添加到defer链表头,表头的defer先执行
在这里插入图片描述
4 执行defer,将defer结构体的参数和返回值拷贝到栈上,打印参数a=1
在这里插入图片描述

(编译阶段,deferproc将defer参数a=1拷贝到堆上,执行时拷贝到栈上defer的参数空间,注意区别defer的参数空间与函数A的局部变量)

先进后出

func Defer(){
	for i:=0;i<5;i++{
		fmt.Println(i)
	}
}
defer : 4
defer : 3
defer : 2
defer : 1
defer : 0

先进后出
遇到panic ,先defer后panic

func defer_call() {
	defer func() { fmt.Println("打印前") }()
	defer func() { fmt.Println("打印中") }()
	defer func() { fmt.Println("打印后") }()

	panic("触发异常")
}
打印后
打印中
打印前
panic: 触发异常

defer语句包含函数的 先执行函数 再defer
在这里插入图片描述

在这里插入图片描述

捕获的变量值被修改/参数逃逸

defer捕获外部变量形成闭包,因为参数被修改,参数a在编译阶段就改为堆分配,栈上存地址,因为要表现闭包内部和外部操作同一参数:
在这里插入图片描述

在这里插入图片描述

1 参数a除了初始化还被修改过,所以逃逸到堆上,栈上只存地址,funcval也只存地址
2 第一步输出3,2,第二部闭包相加相加的时候,addr2指向堆上的地址=3,3+2=5,更新为5,闭包输出栈上局部变量a指向的地址,此时存储的是5

eg:deferproc(没有匿名函数func() ,不涉及闭包)
在这里插入图片描述
1deferproc注册A时a=1,A先要拿到B 的返回值,B将a的值+1返回给A,A再+1,等于3
在这里插入图片描述
go build -gcflags ‘-m -l’ main.go 分析上面代码,出了io,变量a确实没有逃逸
1,defer捕获的变量a=1
2,函数B加1后返回给A

defer嵌套

在这里插入图片描述
1 defer链表存储A2-A1,执行链表时会判断defer是不是自己创建的,判断依据为defer结构体的sp of字段
在这里插入图片描述
2 A2执行,创建两个defer,添加到链表头部,
在这里插入图片描述
3 执行b2,执行后删除链表
在这里插入图片描述

4执行完b1,A2函数检测到下一个链表的defersp不等于自己的sp,A2退出,再次回到A的执行流程

4 defer和return

func main() {

	fmt.Println("user1",DeferFunc1(1))
	fmt.Println("user2",DeferFunc2(1))
	fmt.Println("user3",DeferFunc3(1))
}

func DeferFunc1(i int) (t int) {
	t = i
	defer func() {
		fmt.Println("d1",t)
		t += 3
	}()
	fmt.Println("r1",t)
	return t
}

func DeferFunc2(i int) int {
	t := i
	defer func() {
		fmt.Println("d2",t)
		t += 3
	}()
	fmt.Println("r2",t)

	return t
}

func DeferFunc3(i int) (t int) {
	defer func() {
		fmt.Println("d3",t)
		t += i
	}()
	fmt.Println("r3",t)

	return 2
}

执行顺序

r1 1
d1 1
user1 4
r2 1
d2 1
user2 1
r3 0
d3 2
user3 3

panic和recover

在这里插入图片描述
panic和defer一样,在每一个goroutine中包含panic字段,panic是一个链表,从头部插入
在这里插入图片描述
函数运行时先将A1,A2压入栈,执行到panic后面的代码就不会再执行,协程进入执行defer
在这里插入图片描述
panic触发defer执行时,先将defer结构体的started设置成true,panic设置成当前触发的panic,即panicA
在这里插入图片描述
按链表顺序先执行A2,修改struct的值,执行完A2将其从defer链表移除,继续顺着链表执行A1
在这里插入图片描述
1 执行A1,先将struct中started字段设置为true,panic字段设置为自己(panicA),执行函数A1遇到panicA1,后面的的代码不在执行
2将panicA1插入panic链表头,panicA1再去执行defer链表,发现A1已开始执行,并且 panic字段并不是自己,而是panicA(标defer已经被panicA执行过了,要将其移除)
3 panicA1根据记录的指针找到panicA,把他aborted标记为true,deferA1被释放

在这里插入图片描述
4,defer列表为空,执行panic输出,从后向前执行
panic结构体:
在这里插入图片描述
在这里插入图片描述
recover字段只把panic结构体的recover字段设置为true

panic被recover恢复

在这里插入图片描述
1 发生panic后执行A2,发现recover,把panicA置为已恢复,recover的任务就完成了,程序继续执行
2 A2执行完,检查panic以恢复,从链表移除,deferA2移除,执行A1,程序结束
在这里插入图片描述
接着上面的例子,来看什么情况下panic不会被释放
在这里插入图片描述
1,当defer A2打印后又发生panic,先将panicA2插入panic列表头部,
2,再执行deferA2,发现A2已执行,将A2从列表移除,执行deferA1
在这里插入图片描述
3 A1执行完清除defer列表,打印panic信息
4 先打印painc A,打印时要打印recover信息,再打印panicA2,程序退出

在这里插入图片描述
1 函数A先注册一个panicA ,注册两个defer A1和A2,发生panic后调用gopanic ,将panicA加入panic链表,执行A2
2 函数A2注册defer B1插入链表头,注册panicA2插入链表头,开始执行deferB1
3 函数B1恢复了panic链表头部的panicA2,B1正常结束

恢复过程:
1 B1的recover恢复了panicA2,他要将panic2的recover字段置为true,然后跳出出并移除当前panic

在这里插入图片描述

在这里插入图片描述

go fun( )闭包

1闭包函数内部可以操作外部定义的局部变量
2闭包离开上下文环境也可以执行

闭 包 捕 获 了 局 部 变 量 与 闭 包 函 数 生 产 了 f u n c c a l 结 构 体 , 存 储 在 栈 上 \color{#A0A}{闭包捕获了局部变量与闭包函数生产了funccal结构体,存储在栈上} funccal

3闭包函数代码在编译阶段生成在堆上代码区,因为要捕获变量,闭包结构体在函数执行时生成
4捕获变量的值未被修改,捕获值拷贝,若被修改,则捕获变量的地址

go语言函数可以作为参数,返回值,也可以绑定到变量
在这里插入图片描述
这样的函数称为function value 它是一个结构体,里面存储的也是函数入口地址
在这里插入图片描述
函数运行是在堆上分配一个地址存储fn,指向函数入口

在这里插入图片描述

闭包捕获对外部变量是通过引用的方式实现的;会随着外部变量的改变而修改。为避免此问题可:
通过参数方式传入外部变量;
定义局部变量的方式;

func delayPrint() {
    // 通过参数方式保证每个变量值是不同的;
	for i := 0; i < 3; i++ {
		go func(i int) {
			time.Sleep(time.Second * 1)
			fmt.Println("By param: ", i)
		}(i)
	}
	time.Sleep(time.Second * 4)
	
    // 直接引用外部变量,会发现所有调用最终都捕获了同一个变量值
	for i := 0; i < 3; i++ {
		go func() {
			time.Sleep(time.Second * 1)
			fmt.Println("By clouser: ", i)
		}()
	}
	time.Sleep(time.Second * 4)

    // 通过引入局部变量方式,保证捕获的变量是不同的
	for i := 0; i < 3; i++ {
		tmp := i
		go func() {
			time.Sleep(time.Second * 1)
			fmt.Println("By tmp: ", tmp)
		}()
	}
	time.Sleep(time.Second * 4)
}

// By param:  2
// By param:  0
// By param:  1
// By clouser:  3
// By clouser:  3
// By clouser:  3
// By tmp:  0
// By tmp:  2
// By tmp:  1
————————————————

1.变量值未被修改:

捕获变量c对应两个地址
闭包函数对应一个地址
在这里插入图片描述
1 , m a i n 函 数 有 两 个 局 部 变 量 f 1 , f 2 有 。 一 个 返 回 值 \color{#A0A}{1,main函数有两个局部变量f1,f2 有。一个返回值} 1mainf1f2
2 , c r e a t e 函 数 有 一 个 局 部 变 量 c = 2 \color{#A0A}{2,create函数有一个局部变量c=2} 2createc=2
3 , 函 数 运 行 时 在 堆 上 创 建 f u n c t i o n v a l u e 结 构 体 , 包 含 闭 包 入 口 地 址 和 闭 包 捕 获 变 量 c \color{#A0A}{3,函数运行时在堆上创建function value结构体,包含闭包入口地址和闭包捕获变量c} 3functionvaluec

2变量值被修改

在这里插入图片描述
main的局部变量fs是长度为2的数组
有两个返回值
c r e a t e 函 数 的 局 部 变 量 因 为 被 捕 获 所 以 在 堆 上 分 配 \color{#A0A}{create函数的局部变量因为被捕获所以在堆上分配} create

在这里插入图片描述
在这里插入图片描述
1两次循环闭包的捕获变量存储的都是地址,for执行完i的值是2,然后这个地址写入了返回值
2返回值拷贝给fs的时候,这个捕获变量i地址指向的值就是2
3调用fs[i]的时候,addr0,addr1一次写入寄存器,两个闭包捕获变量地址指向同一个位置,值是2

在这里插入图片描述

返回值是闭包 捕获了自己

在这里插入图片描述
1main调用x时 x有一个返回值,返回值是funcval
2 func x返回值捕获了y,所以y是有捕获列表的funcval
3 因为捕获的变量y一开始定义了func,return时又做了修改,所以捕获了地址
在这里插入图片描述
4 当main调用x()返回y时,y保存在堆上,返回值 这里就变成了y的地址&y,这与func x()的定义不符,
在这里插入图片描述
5 这样编译器会在堆上保存y的副本y’,同时为x生成一个局部变量py‘记录y’的地址
在这里插入图片描述
6 这样只需要在return时将y’的值拷贝到返回值空间,y’和y都指向的是堆上的funcval fn
在这里插入图片描述
调用y指向的就是y’,捕获的变量也是y‘’,输出z

panic和recover

在这里插入图片描述
recover捕捉panic信息,panic之前压入栈的defer会执行,没有入栈的不执行
在这里插入图片描述
defer按先入后出执行 recover捕捉第一个panic

context

Context 的结构非常简单,它是一个接口。
在这里插入图片描述

在这里插入图片描述
timerctx,封装的timer和deadline的cancelctx,定时或在deadline时取消ctx
在这里插入图片描述
当然会涉及到节点管理
在这里插入图片描述

Context 提供跨越API的截止时间获取,取消信号,以及请求范围值的功能。//
它的这些方案在多个 goroutine 中使用是安全的

  type Context interface {
   // 如果设置了截止时间,这个方法ok会是true,并返回设置的截止时间 
   Deadline() (deadline time.Time, ok bool)
    // 如果 Context 超时或者主动取消返回一个关闭的channel,如果返回的是nil,表示这个 
    // context 永远不会关闭,比如:Background() 
    Done() <-chan struct{} 
    // 返回发生的错误 
    Err() error
     // 它的作用就是传值
     Value(key interface{}) interface{}
     }
 写到这里,我们打住想一想,如果你来实现这样一个能力的 package,你抽象的接口是否也是具备这样四个能力?

获取截止时间
获取信号
获取信号产生的对应错误信息
传值专用

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值