• 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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    defer : 4
    defer : 3
    defer : 2
    defer : 1
    defer : 0
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    func defer_call() {
    	defer func() { fmt.Println("打印前") }()
    	defer func() { fmt.Println("打印中") }()
    	defer func() { fmt.Println("打印后") }()
    
    	panic("触发异常")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    打印后
    打印中
    打印前
    panic: 触发异常
    
    • 1
    • 2
    • 3
    • 4

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    执行顺序

    r1 1
    d1 1
    user1 4
    r2 1
    d2 1
    user2 1
    r3 0
    d3 2
    user3 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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{}
         }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
     写到这里,我们打住想一想,如果你来实现这样一个能力的 package,你抽象的接口是否也是具备这样四个能力?
    
    • 1

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

  • 相关阅读:
    twitter推文案例
    单机、集群和分布式
    Python中安装Beautiful Soup库及其相关解析器的方法2-1
    蓝桥杯每日一题2023.10.17
    python之集合的创建与使用,遍历,集合常见的操作函数,集合与列表,元组,字典的嵌套
    web:[GXYCTF2019]Ping Ping Ping
    源码深度剖析SpringBoot启动流程中开启OpenFeign的入口(ImportBeanDefinitionRegistrar详解)
    重学c#系列——枚举[二十三]
    java springboot测试类鉴定虚拟MVC运行值与预期值是否相同
    金融信创与云化转型|保险超融合架构转型与场景探索合集
  • 原文地址:https://blog.csdn.net/weixin_41479678/article/details/125498906