zoukankan      html  css  js  c++  java
  • Go语言 defer和逃逸分析

    defer

    什么是defer?

       defer是Go语言的一中用于注册延迟调用的机制,使得函数活语句可以再当前函数执行完毕后执行

    为什么需要defer?

      Go语言提供的语法糖,减少资源泄漏的发生

    如何使用defer?

      在创建资源语句的附近,使用defer语句释放资源

    示例一:

    /*func f1()(r int){
    	t := 5
    	// 1.赋值指令
    	r = t
     	// defer在赋值和返回之间执行
    	defer func() {
    		t = t + 5
    	}()
    	// 空的return指令
    	return t
    }*/
    
    package main
    
    import "fmt"
    
    func f1()(r int){
    	t := 5
    	defer func() {
    		t = t + 5
    	}()
    	return t
    }
    
    func main()  {
    	fmt.Println(f1())
    }
    
    //5

     执行return指令时,首先会把返回值copy到栈上,返回空的IP指令;return时先执行了赋值操作r=t,后执行defer操作,最后r的值没有修改

    示例二:

    package main
    
    import "fmt"
    
    func f2()(r int){
    	defer func() {
    		r = r + 5
    	}()
    	return 1
    }
    
    func main()  {
    	fmt.Println(f2())
    }
    
    //6
    

    能懂示例一,这个自然懂

    示例三:

    package main
    
    import "fmt"
    
    func f3()(r int){
    	defer func(r *int) {
    		*r = *r + 5
    	}(&r)
    	return 1
    }
    
    func main()  {
    	fmt.Println(f3())
    }
    
    //6
    

    defer传入的是指针,修改会改变原来的值,所以依然是6

    示例四:

    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    func e1(){
    	var err error
    	// 压栈的时候 err已经变成nil值
    	defer fmt.Println("e1",err)
    	err = errors.New("defer1 error")
    	fmt.Println(err)
    	return
    }
    
    func e2(){
    	var err error
    	// 闭包err是外部err的引用
    	defer func() {
    		fmt.Println("e2",err)
    	}()
    	err = errors.New("defer2 error")
    	return
    }
    
    func e3(){
    	var err error
    	// 参数拷贝时就是nil
    	defer func(err error) {
    		fmt.Println("e3",err)
    	}(err)
    	err = errors.New("defer3 error")
    	return
    }
    
    
    func main()  {
    	e1()
    	e2()
    	e3()
    }
    
    //e1 <nil>
    //e2 defer2 error
    //e3 <nil>
    

    示例五:

    package main
    
    import "fmt"
    
    func main()  {
    	var a = accumulator()
    	fmt.Println(a(1))
    	fmt.Println(a(10))
    	fmt.Println(a(100))
    	var b = accumulator()
    	fmt.Println(b(1))
    	fmt.Println(b(10))
    	fmt.Println(b(100))
    }
    
    func accumulator() func(int) int{
    	var x int
    
    	return func(i int) int {
    			fmt.Printf("(%+v, %+v) - ",&x,x)
    			x += i
    			return x
    	}
    
    }
    
    //(0xc00000a0b8, 0) - 1
    //(0xc00000a0b8, 1) - 11
    //(0xc00000a0b8, 11) - 111
    //(0xc00000a120, 0) - 1
    //(0xc00000a120, 1) - 11
    //(0xc00000a120, 11) - 111
    

      

    示例六(执行顺序):

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main()  {
    	defer  fmt.Println("defer main")
    	var user = ""
    
    	go func() {
    		defer func() {
    			fmt.Println("defer caller")
    			if err := recover(); err != nil{
    				fmt.Println("recover success . err:", err)
    			}
    		}()
    
    		func(){
    			defer func() {
    				fmt.Println("defer here")
    			}()
    			if user == ""{
    				panic("should set user env.")
    			}
    		}()
    	}()
    	time.Sleep(time.Second)
    	fmt.Println("end")
    
    }
    

      

     示例七:

    package main
    
    import "fmt"
    
    func main() {
    
    	for i := 0; i < 5; i++ {
    		defer fmt.Println(i,1)
    
    	}
    
    	for i := 0; i < 5; i++ {
    		defer func(){
    			fmt.Println(i,2)
    		}()
    	}
    
    	for i := 0; i < 5; i++ {
    		defer func(){
    			j := i
    			fmt.Println(j,3)
    		}()
    	}
    
    	for i := 0; i < 5; i++ {
    		j := i
    		defer fmt.Println(j,4)
    	}
    
    	for i := 0; i < 5; i++ {
    		j := i
    		defer func() {
    			fmt.Println(j,5)
    		}()
    	}
    
    	// 拷贝传值
    	for i := 0; i < 5; i++ {
    		defer func(j int) {
    			fmt.Println(j, 6)
    		}(i)
    	}
    }
    
    //4 6
    //3 6
    //2 6
    //1 6
    //0 6
    //4 5
    //3 5
    //2 5
    //1 5
    //0 5
    //4 4
    //3 4
    //2 4
    //1 4
    //0 4
    //5 3
    //5 3
    //5 3
    //5 3
    //5 3
    //5 2
    //5 2
    //5 2
    //5 2
    //5 2
    //4 1
    //3 1
    //2 1
    //1 1
    //0 1
    //
    

      

    逃逸分析

    什么是逃逸分析?

      Go语言编译器执行静态代码分析后,决定哪些变量逃逸到堆上

    为什么需要逃逸分析?

      尽可能将变量分配到栈上

    逃逸分析如何进行?

      只有在编译器能证明变量在函数返回后不再被引用的,才会分配到栈上,其他情况分配到堆上

    总结:

      动态内存分配(堆上)比静态内存分配(栈上)开销要大的多

      如果变量在函数外部没有引用,则优先放到栈中;如果在函数外部存在引用,则必定放到堆中;

    示例一:

    package main
    
    type S1 struct {}
    
    func main()  {
    	var x S1
    	_ = indentity1(x)
    }
    
    func indentity1(x S1) S1{
    	return x
    }

     逃逸检查

    Z:srcdefer和逃逸分析>go build -gcflags "-m -l" escape1.go
    
    Z:srcdefer和逃逸分析>

    没有逃逸,值传递,直接在栈上分配。Go语言函数传递都是通过值的,调用函数的时候,直接在栈上copy出一份参数,不存在逃逸

    示例二:

    package main
    
    type S2 struct {}
    
    func main()  {
    	var x S2
    	y := &x
    	_ = indentity2(y)
    }
    
    func indentity2(x *S2) *S2{
    	return x
    }
    

    逃逸检查 

    Z:srcdefer和逃逸分析>go build -gcflags "-m -l" escape2.go
    # command-line-arguments
    .escape2.go:11:17: leaking param: x to result ~r1 level=0
    .escape2.go:7:7: main &x does not escape
    
    Z:srcdefer和逃逸分析>
    

    x未发生逃逸,identity函数的输入直接当成返回值了,没有对x进行引用,所以x没有逃逸。

    示例三:

    package main
    
    type S3 struct {}
    
    func main()  {
    	var x S3
    	_ = *ref3(x)
    }
    
    func ref3(z S3) *S3{
    	return &z
    }
    

    逃逸检查

    Z:srcdefer和逃逸分析>go build -gcflags "-m -l" escape3.go
    # command-line-arguments
    .escape3.go:11:9: &z escapes to heap
    .escape3.go:10:11: moved to heap: z
    
    Z:srcdefer和逃逸分析>
    

      

    示例四:

    package main
    
    type S4 struct {
    	M *int
    }
    
    func main()  {
    	var i int
    	_ = ref4(i)
    }
    
    func ref4(y int) (z S4){
    	z.M = &y
    	return z
    

    逃逸检查

    Z:srcdefer和逃逸分析>go build -gcflags "-m -l" escape4.go
    # command-line-arguments
    .escape4.go:13:8: &y escapes to heap
    .escape4.go:12:11: moved to heap: y
    
    Z:srcdefer和逃逸分析>
    

      

    示例五:

    package main
    
    type S5 struct {
    	M *int
    }
    
    func main()  {
    	var i int
    	ref5(&i)
    }
    
    func ref5(y *int) (z S5){
    	z.M = y
    	return z
    }A
    

    逃逸检查 

    Z:srcdefer和逃逸分析>go build -gcflags "-m -l" escape5.go
    # command-line-arguments
    .escape5.go:12:11: leaking param: y to result z level=0
    .escape5.go:9:7: main &i does not escape
    
    Z:srcdefer和逃逸分析>
    

      

    示例六:

    package main
    
    type S6 struct {
    	M *int
    }
    
    func main()  {
    	var x S6
    	var i int
    	ref6(&i,&x)
    }
    
    func ref6(y *int, z *S6){
    	z.M = y
    }
    

    逃逸检查

    Z:srcdefer和逃逸分析>go build -gcflags "-m -l" escape6.go
    # command-line-arguments
    .escape6.go:13:11: leaking param: y
    .escape6.go:13:19: ref6 z does not escape
    .escape6.go:10:7: &i escapes to heap
    .escape6.go:9:6: moved to heap: i
    .escape6.go:10:10: main &x does not escape
    

      

  • 相关阅读:
    mybatis 中 使用 allowMultiQueries=true
    接口安全性的几种方法
    springboot 常用配置文件
    使用Nginx简单实现负载均衡
    Nginx的负载均衡
    传输数据校验算法研究
    程序员必备的代码审查(Code Review)清单
    百度地图JavascriptApi Marker平滑移动及车头指向行径方向
    《互联网MySQL开发规范》
    JS原生Date类型方法的一些冷知识
  • 原文地址:https://www.cnblogs.com/lianzhilei/p/11922577.html
Copyright © 2011-2022 走看看