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