go-defer的用法及执行原理

Go 中有几个比较特殊的关键字,如 defer,尤其 defer+panic+recover的组合可以发挥出 java 中 try...catch...fanilly 的作用,功能非常强大,值的去深入学习。同时他们每个又有自身的特性,这一节我们先来理解一下 defer

defer 关键字在 go 中的使用率算是非常高的,类似于 finally 与 析构函数的作用,用来做方法的善后工作。

1. 怎么使用?

defer 后面会接受一个函数,但该函数不会立刻被执行,而是等到包含它的程序返回时(包含它的函数执行了return语句、运行到函数结尾自动返回、对应的goroutine panic),defer函数才会被执行。通常用于资源释放、打印日志、异常捕获等

下面看一段打开文件的🌰:

func OpenFile()  {
	file, err := os.Open("/home/demo/info.txt")
	if err != nil {
		fmt.Println(err)
	}

	// 这里将 defer 放在 err 判断的后面,而不是 os.Open() 的后面
	// 因为若 err != nil ,文件打开是失败的, 没必要释放
	// 若 err != nil, file 有可能为 nil ,这时候释放资源可能会导致程序崩溃
	defer file.Close()
}

2. 理解 defer 的执行时机

下面有一段相对复杂的 defer 定义,这个方法里面有定义 4 个 defer , 分别以不同的方式来定义 defer 后的函数

func deferTest() {
	fish := 0
	
	defer func() {
		fmt.Println("d1: ", fish)	
	}()
	
	defer fmt.Println("d2: ", fish) 
	
	defer func(fish int) {
		fish += 2						
		fmt.Println("d3: ", fish)	
	}(fish)							    
	
	defer func() {
		fmt.Println("d4: ", fish)	
		fish +=2						
	}()
  
	fish++
}

这里可以先不要往下看答案,或者直接本地运行看结果,可以先猜猜每个结果的值,然后去跟真实结果做对比

如果是新手的话,可能会这么认为:d1: 1, d2: 1, d3: 3, d4: 3 , 这肯定是错的啦,要不然我还讲什么呢?哈哈

首先要明白一点:一个方法中声明了多个defer, 那么 defer 是按顺序从后往前执行的

根据这个规则,我们再猜想一次结果:d4: 1, d3:5, d2: 5, d1: 5, 真的对么? 我们来看看真正的结果

d4:  1
d3:  2
d2:  0
d1:  3

这个结果是不是很奇怪,无论你怎么想都想不出来,因为它牵涉到 defer 声明时的一些特殊规则,defer 后面的函数对外部参数有两种引用方式:

  • 参数传递:在defer声明时,即将值传递给defer,并缓存起来,调用defer的时候使用缓存值进行计算
  • 直接引用:根据上下文确定当前值,作用域与外部变量相同

d1,d4 是在闭包里面直接引用,而 d2,d3 是经过参数传递来饮用的,因此 d2,d3 传递进去的初始值都是 0

func deferTest() {
  // 1. 声明 fish=0
	fish := 0
  
	// 直接引用(闭包)
  //
	defer func() {
    // 8. 打印 fish=3
		fmt.Println("d1: ", fish)			// 3
	}()
  
	// 参数传递
  // 2. 传递 fish=0
  // 7. 打印内部 fish 0
	defer fmt.Println("d2: ", fish) // 0
  
	// 参数传递(闭包)
  // 3. 传递 fish=0
	defer func(fish int) {
    // 6. 内部 fish 0 + 2 =2
		fish += 2											// fish 只作用于内部
    // 7. 打印内部 fish
		fmt.Println("d3: ", fish)			// 2
	}(fish)							    				// 声明时传递, fish = 0
  
	// 直接引用(闭包)
	defer func() {
    // 4. 打印 fish = 1
		fmt.Println("d4: ", fish)			// 1
    // 5. fish = 3
		fish +=2											// fish = 3, 作用域与外部的相同
	}()
  
  // 3. fish=1
	fish++
}

3. defer 与 return 返回值的关系

再看一段代码,猜猜下面3个方法的返回值各是多少

func defer1() (res int) {
	defer func() {
		res ++
	}()
	return 10
}

func defer2() (res int) {
	sb := 10
	defer func() {
		sb += 5
	}()
	return sb
}

func defer3() (res int) {
	res = 2
	defer func(res int) {
		res += 2
		fmt.Println("内部 res ", res)  
	}(res)		
	return 10
}

接下来我们一个个看,也许你会认为defer1 的结果是10,其实是11,在这里我们先明白一个概念:

return 语句并不是一条原子指令,有没有被震慑到!!!

有返回值的且带有 defer 函数的方法中, return 语句执行顺序:

1. 返回值赋值
2. 调用 defer 函数 (在这里是可以修改返回值的)
3. return 返回值

defer1 方法可以这样解析:

// 不是 10 , 是 11
func defer1() (res int) {
	defer func() {
		// 2. res = 10 + 1 = 11
		res ++
	}()
	// 1. res = 10
  // 3. return res
	return 10
}

也就是说最后的值为 11

defer2 返回值不是15,而是10,这样解析

// 不是15, 是10
func defer2() (res int) {
	// 1. sb = 10
	sb := 10
	defer func() {
		// 3. sb = 15, 但是 res = 10
		sb += 5
	}()
	// 2. res = sb = 10
  // 4. return res(10)
	return sb
}

defer3 的返回值不是12, 而是10, 解析如下:

// 不是12,是10
func defer3() (res int) {
  // 1. res=2
	res = 2
	defer func(res int) {
    // 3. 内部res为形参,不影响外边的值 res=2+2=4
		res += 2
		fmt.Println("内部 res ", res)   // 4
	}(res)		// defer 参数的值是在声明的时候确定的,也就是只有 defer 之前的语句会影响这个值
	// 2. res = 10
  // 4. return res(10)
	return 10
}
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值