Golang defer小结

1. 概念

defer: 延迟调用

  • FILO 先进后出
  • 即使函数发生panic错误,也会执行
  • 支持匿名函数调用
  • 用于资源清理、文件关闭、解锁以及记录时间等操作
  • 与匿名函数配合,可在return后修改函数的计算结果
func main() {
	for i := 0; i < 3; i++ {
		defer fmt.Println(i)   // 2 1 0
	}

	for i := 0; i < 3; i++ {
		defer func() {
			fmt.Println(i)     // 3 3 3
		}()
	}
}

2. 循环

延迟调用在函数结束时调用,如果将其放到循环中,会造成资源浪费

func main() {
	for i := 0; i < 1000; i++ {
		path := fmt.Sprintf("%04d.txt", i)
		f, err := os.Open(path)
		if err != nil {
			log.Println(err)
			continue
		}

		defer f.Close()

		// do something
	}
}

优化:

func main() {
	do := func(i int) {
		path := fmt.Sprintf("%04d.txt", i)

		f, err := os.Open(path)
		if err != nil {
			log.Println(err)
			return
		}

		defer f.Close()

		// do something
	}

	for i := 0; i < 1000; i++ {
		do(i)
	}
}

3. 闭包

func main() {
	var fns = [3]func(){}

	for i := 0; i < 3; i++ {
		// 4th, defer i = 2, 1, 0
		defer fmt.Println("defer i =", i)

		// 3rd, closure_without_arg_defer i = 3, 3, 3
		defer func() {
			fmt.Println("closure_without_arg_defer i =", i)
		}()

		// 2nd, closure_with_arg_defer i = 2, 1, 0
		defer func(i int) {
			fmt.Println("closure_with_arg_defer i =", i)
		}(i)

		fns[i] = func() {
			fmt.Println("closure i =", i)
		}
	}

	// 1st, but always "closure i = 3"
	for _, fn := range fns {
		fn()
	}
}

4. 性能

相比直接用CALL汇编指令调用函数,延迟调用则须花费更大代价。这其中包括注册、调用等操作,还有额外的缓存开销。

var m sync.Mutex

func call() {
	m.Lock()
	m.Unlock()
}

func deferCall() {
	m.Lock()
	defer m.Unlock()
}

func BenchmarkCall(b *testing.B) {
	for i := 0; i < b.N; i++ {
		call()
	}
}

func BenchmarkDeferCall(b *testing.B) {
	for i := 0; i < b.N; i++ {
		deferCall()
	}
}
$ go test -bench=. -v
BenchmarkCall
BenchmarkCall-4         94623559                12.5 ns/op
BenchmarkDeferCall
BenchmarkDeferCall-4    70848210                16.0 ns/op

5. 复杂表达式

只延迟紧跟 defer 的函数,并将该函数参数拷贝值入栈存起来

func calc(idx string, a, b int) int {
	ret := a + b
	fmt.Println(idx, a, b, ret)
	return ret
}

func main() {
	a, b := 1, 2

	defer calc("1", a, calc("10", a, b))

	a = 0

	defer calc("2", a, calc("20", a, b))

	b = 1
}

// Output:
// 10 1 2 3
// 20 0 2 2
// 2  0 2 2
// 1  1 3 4

defer 后有多个表达式,只保留最后需要执行的那一个

type Slice []int

func NewSlice() Slice {
	return make([]int, 0)
}

func (s *Slice) Add(i int) *Slice {
	*s = append(*s, i)
	//fmt.Println(*s)
	return s
}

func test(s *Slice) {
	defer s.Add(1).Add(2)

	s.Add(3)

	fmt.Println(*s) // [1, 3]
}

func main() {
	s := NewSlice()

	test(&s)

	fmt.Println(s) // [1, 3, 2]
}

6. 函数返回值

Golangdeferreturn、返回值之间执行顺序坑:

  1. 多个defer的执行顺序为“后进先出”;
  2. deferreturn、返回值三者的执行逻辑应该是:return最先执行,return负责将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出。
func func1(i int) (t int) {
	t = i
	defer func() {
		t += 3
	}()

	return t
}

func func2(i int) int {
	t := i
	defer func() {
		t += 3
	}()

	return t
}

func func3(i int) (t int) {
	defer func() {
		t += i
	}()

	// t 指向的对象
	return 2
}

func main() {
	fmt.Println(func1(1)) // 4
	fmt.Println(func2(1)) // 1
	fmt.Println(func3(1)) // 3
}

其他类似示例:

// 1
func f1() (r int) {
	defer func() {
		r++
	}()
	return 0
}

// 1
func f2() (r int) {
	i := 1
	defer func() {
		i++
	}()

	return i
}

// 2
func f3() (r int) {
	defer func(r int) {
		r++
	}(r)

	return 2
}

// 3
func f4() (r int) {
	i := 1
	defer func() {
		r = i + 2
	}()

	return i
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值