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:\src\defer和逃逸分析>go build -gcflags "-m -l" escape1.go

Z:\src\defer和逃逸分析>

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

 

示例二:

package main

type S2 struct {}

func main()  {
	var x S2
	y := &x
	_ = indentity2(y)
}

func indentity2(x *S2) *S2{
	return x
}

逃逸检查 

Z:\src\defer和逃逸分析>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:\src\defer和逃逸分析>

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

 

示例三:

package main

type S3 struct {}

func main()  {
	var x S3
	_ = *ref3(x)
}

func ref3(z S3) *S3{
	return &z
}

逃逸检查

Z:\src\defer和逃逸分析>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:\src\defer和逃逸分析>

  

示例四:

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:\src\defer和逃逸分析>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:\src\defer和逃逸分析>

  

示例五:

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:\src\defer和逃逸分析>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:\src\defer和逃逸分析>

  

示例六:

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:\src\defer和逃逸分析>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

  

posted @ 2019-11-24 15:24  Assassinの  阅读(369)  评论(0编辑  收藏  举报