golang中的defer函数理解

目录
  • golang的defer
    • 什么是defer
    • 理解defer
      • defer什么时间执行(defer、 return、返回值 三者的执行顺序)
      • defer输出的值,就是定义时的值。而不是defer真正执行时的变量值(注意引用情况)
      • 多个defer,执行顺序
    • defer的函数一定会执行么?
      • panic情况
      • os.Exit情况
      • kill情况(Ctrl+C)
  • 参考文献

golang的defer

什么是defer

defer的的官方文档:https://golang.org/ref/spec#Defer_statements

go语言中defer可以完成延迟功能,当前函数执行完成后再执行defer的代码块。通过defer,我们可以在代码中优雅的关闭/清理代码中所使用的变量。

defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源。

Go语言机制担保一定会执行defer语句中的代码。其它语言中也有类似的机制,比如Java、C#语言里的finally语句,C++语言里的析构函数(Destructor)可以起类似的作用,C++语言机制担保在对象被销毁前一定会执行析构函数中的代码。C++中的析构函数析构的是对象,Go中的defer析构的是函数。

理解defer

defer什么时间执行(defer、 return、返回值 三者的执行顺序)

defer只有在当前函数执行完毕后,才会执行。描述其实不太精确

go中的return语句并不是原子性操作,一般是分为两步:

  • 将返回值赋值给一个变量
  • 执行RET指令

return并不是原子性操作,是通过一个变量赋值和ret指令来完成的。defer就执行在1之后,2之前。defer的执行顺序在return之后,但是在返回值返回给调用方之前,所以使用defer可以达到修改返回值的目的。

defer、 return、返回值 三者的执行顺序是 : return 最先给返回值赋值;接着 defer 开始执行一些收尾工作;最后 RET 指令携带返回值退出函数。

package main

import (
    "fmt"
)

func main() {
    ret := test()
    fmt.Println("test return:", ret)
}
// func test() ( int) {  这种就是匿名返回值
//返回值改为命名返回值, 具名返回值。即返回值带有名字, 这样我们在执行defer的时候相当于修改了返回值的值
func test() (i int) {
    //var i int

    defer func() {
        i++
        fmt.Println("test defer, i = ", i)
    }()

    return i
}

注意: 这块验证使用了具名返回值 func test() (i int) { 中的(i int) 。 测试结果满足我们预期。

编码中,我们要特别注意, go语言中匿名返回值和命名返回值对defer的影响。不过一般我们都是使用命名返回值。

一个主函数拥有一个匿名的返回值,返回时使用字面值,比如返回”1”、”2”、”Hello”这样的值,这种情况下defer语句是无法操作返回值的。

defer输出的值,就是定义时的值。而不是defer真正执行时的变量值(注意引用情况)

defer函数会在return之后被调用。那么这段函数执行完之后,是不用应该输出1呢?

package main

import "fmt"

func test1() {
	i := 0
	defer fmt.Println(i)
	i++
	return
}

func main() {
	test1()
}

输出结果:0

虽然我们在defer后面定义的是一个带变量的函数: fmt.Println(i). 但这个变量(i)在defer被声明的时候,就已经确定其确定的值了。

总结: 因为defer后面的函数在入栈的时候保存的是入栈那一刻的值,而当时i的值是0,所以后期对i修改,并不会影响栈内函数的值。

我们再看下一个例子:

package main

import "fmt"

func test2() {
	x := 10
	defer func(a *int) {
		fmt.Println(*a)
	}(&x)
	x++
}

func main() {
	test2()
}

输出结果: 11

这里为什么和前面结论不一样呢?
这里defer后面函数入栈的时候存入的执行变量x的指针。所以,后期x值改变的时候,输出结果也会改变。

总结: 需要注意引用情况。对于指针类型参数,规则仍然适用,只不过延迟函数的参数是一个地址值,这种情况下,defer后面的语句对变量的修改会影响延迟函数。

多个defer,执行顺序

package main

import (
    "fmt"
)

func main() {
    defer fmt.Println("main defer1")
    test()
    defer fmt.Println("main defer2")
}

func test() () {
    defer func() {
        fmt.Println("test defer1")
    }()
    defer func() {
        fmt.Println("test defer2")
    }()
}

输出结果:

test defer2
test defer1
main defer2
main defer1

总结: 后进先出(LIFO)的顺序执行,即先出现的 defer 最后执行。即:多个defer语句的执行顺序是逆序执行。

defer的函数一定会执行么?

defer是Go语言中的延迟执行语句,用来添加函数结束时执行的代码,常用于释放某些已分配的资源、关闭数据库连接、断开socket连接、解锁一个加锁的资源。

Go语言机制担保一定会执行defer语句中的代码。其它语言中也有类似的机制,比如Java、C#语言里的finally语句,C++语言里的析构函数(Destructor)可以起类似的作用,C++语言机制担保在对象被销毁前一定会执行析构函数中的代码。C++中的析构函数析构的是对象,Go中的defer析构的是函数。

panic情况

网上demo:

package main

import (
	"fmt"
)

func test1() {
	fmt.Println("test")
}

func test2() {
	panic(1)
}
func main() {
	fmt.Println("main start")
	defer test1()
	test2() //造panic
	fmt.Println("main end")
}

执行结果:

main start
test                                                                  
panic: 1                                                                                                                                   
goroutine 1 [running]:                                                
main.test2(...)

总结: 我们发现正常的panic,还是会调我们的defer的,并且在会在panic之前执行。

os.Exit情况

网上demo:

package main

import (
	"fmt"
	"os"
)

func test1() {
	fmt.Println("test")
}

func main() {
	fmt.Println("main start")
	defer test1()
	fmt.Println("main end")
	os.Exit(0)
}

执行结果:

main start
main end

总结: 如果在当前函数里是因为执行了os.Exit退出,而不是正常return退出或者panic退出,那程序会立即停止,被defer的函数调用不会执行。

kill情况(Ctrl+C)

package main

import (
	"fmt"
	"time"
)

func test1() {
	fmt.Println("test")
}

func test2() {
	time.Sleep(60 * time.Second)
}
func main() {
	fmt.Println("main start")
	defer test1()
	test2()
	fmt.Println("main end")
}

执行结果:

main start

Process finished with the exit code -1073741510 (0xC000013A: interrupted by Ctrl+C)

如上,我们test2() 睡眠时间内,点击Ctrl+C,发现defer test1()并没有执行。

总结:这个点很重要,需要我们在日常异常中断时,留意defer是否未处理的情况。
所以一般情况下,我们程序需要捕获这种异常中断,在程序退出前,手动做一些处理。

参考文献

Go常见坑:Go语言里被defer的函数一定会执行么?
参考URL: https://blog.csdn.net/perfumekristy/article/details/121343642
面试官:听说你精通golang的defer?
参考URL: https://cloud.tencent.com/developer/article/2076951

到此这篇关于golang中的defer函数理解的文章就介绍到这了,更多相关golang defer函数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 聊聊golang中多个defer的执行顺序

    golang 中多个 defer 的执行顺序 引用 Ture Go 中的一个示例: package main import "fmt" func main() { fmt.Println("counting") for i := 0; i < 10; i++ { defer fmt.Println(i) } fmt.Println("done") } 程序执行结果为: counting done 9 8 7 6 5 4 3 2 1 0 从结

  • 详解golang defer 闭包 匿名函数

    目录 defer的触发时机 defer,return,返回值的执行顺序 闭包与匿名函数 defer用于资源的释放,会在函数返回之前进行调用.如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用. defer的触发时机 包裹着defer语句的函数返回时 包裹着defer语句的函数执行到最后时 当前goroutine发生Panic时 当前goroutine发生Panic时 //输出结果:return前执行defer func f1() { defer fmt.Printl

  • Golang学习笔记之延迟函数(defer)的使用小结

    golang的defer优雅又简洁, 是golang的亮点之一.defer在声明时不会立即执行,而是在函数return后,再按照先进后出的原则依次执行每个defer,一般用于释放资源.清理数据.记录日志.异常处理等. 关键字defer于注册延迟调用.这些调用直到 ret 前才被执行,通常用于释放资源或错误处理. 一.当defer被声明时,其参数就会被实时解析 func a() { i := 0 defer fmt.Println(i) //输出0,因为i此时就是0 i++ defer fmt.P

  • Golang巧用defer进行错误处理的方法

    本文主要跟大家介绍了Golang巧用defer进行错误处理的相关内容,分享出来供大家参考学习,下面来看看详细的介绍: 问题引入 毫无疑问,错误处理是程序的重要组成部分,有效且优雅的处理错误是大多数程序员的追求.很多程序员都有C/C++的编程背景,Golang的程序员也不例外,他们处理错误有意无意的带着C/C++的烙印. 我们看看下面的例子,就有一种似曾相识的赶脚,代码如下: func deferDemo() error { err := createResource1() if err != n

  • golang中的defer函数理解

    目录 golang的defer 什么是defer 理解defer defer什么时间执行(defer. return.返回值 三者的执行顺序) defer输出的值,就是定义时的值.而不是defer真正执行时的变量值(注意引用情况) 多个defer,执行顺序 defer的函数一定会执行么? panic情况 os.Exit情况 kill情况(Ctrl+C) 参考文献 golang的defer 什么是defer defer的的官方文档:https://golang.org/ref/spec#Defer

  • golang中strconv.ParseInt函数用法示例

    本文实例讲述了golang中strconv.ParseInt函数用法.分享给大家供大家参考,具体如下: golang strconv.ParseInt 是将字符串转换为数字的函数,功能灰常之强大. 参数1 数字的字符串形式 参数2 数字字符串的进制 比如二进制 八进制 十进制 十六进制 参数3 返回结果的bit大小 也就是int8 int16 int32 int64 func ParseInt(s string, base int, bitSize int) (i int64, err erro

  • Golang中的自定义函数详解

    不管是面向过程的编程,还是面向对象的编程,都离不开函数的概念,分别是,参数,函数名,返回值.接下来我们看看Go语言在这三个方面是做怎么操作的吧. 参数 谈到参数,我们在写函数或者是类中的方法的时候都需要考虑我们应该传递怎样的参数,或者是是否需要参数. 参数首先分为无参函数有参.无参也就是没有参数,也就不用写了. 有参 func functionTest() {  # 小括号内就是用来放参数的     # 函数体内 } Go语言是强数据类型的语言,参数是要指定类型的不然就报错.func 是函数的声

  • Golang的关键字defer的使用方法

    目录 核心思想 defer链 源码分析 优化 核心思想 在defer出现的地方插入了指令CALL runtime.deferproc,在函数返回的地方插入了CALL runtime.deferreturn.goroutine的控制结构中,有一张表记录defer,调用runtime.deferproc时会将需要defer的表达式记录在表中,而在调用runtime.deferreturn的时候,则会依次从defer表中“出栈”并执行 如果有多个defer,调用顺序类似栈,越后面的defer表达式越先

  • Golang中panic的异常处理

    目录 前言 如何恢复panic造成的程序崩溃 何时使用panic 前言 Golang中当程序发生致命异常时(比如数组下标越界,注意这里的异常并不是error),Golang程序会panic(运行时恐慌).当程序发生panic时,程序会执行当前栈中的defer 函数列表.然后打印引发panic的具体信息,最后进程退出,本篇文章我们一起探讨Golang中的panic以及如何利用defer 和 recover 来恢复这种致命的异常 分析造成panic堆栈信息 func main() {     f1(

  • Golang中如何使用lua进行扩展详解

    前言 最近在项目中需要使用lua进行扩展,发现github上有一个用golang编写的lua虚拟机,名字叫做gopher-lua.使用后发现还不错,借此分享给大家,下面话不多说了,来一起看看详细的介绍吧. 数据类型 lua中的数据类型与golang中的数据类型对应关系作者已经在文档中说明,值得注意的是类型是以L开头的,类型的名称是以LT开头的. golang中的数据转换为lua中的数据就必须转换为L开头的类型: str := "hello" num := 10 L.LString(st

  • GoLang中panic与recover函数以及defer语句超详细讲解

    目录 一.运行时恐慌panic 二.panic被引发到程序终止经历的过程 三.有意引发一个panic并让panic包含一个值 四.施加应对panic的保护措施从而避免程序崩溃 五.多条defer语句多条defer语句的执行顺序 一.运行时恐慌panic panic是一种在运行时抛出来的异常.比如"index of range". panic的详情: package main import "fmt" func main() { oneC := []int{1, 2,

  • golang中defer的使用规则详解

    前言 在golang当中,defer代码块会在函数调用链表中增加一个函数调用.这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用.因此,defer通常用来释放函数内部变量. 为了更好的学习defer的行为,我们首先来看下面一段代码: func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil {

  • golang中defer的基本使用教程

    目录 前言 1.什么是defer 2.defer的特点 3.defer什么时间执行 4.defer常见的坑 1.输出是多少? 2.输出多少 3.输出多少 4.输出什么 总结 前言 第一次看go基础语法的时候,用使用到了defer.但是一直不知道它到底是什么,有什么用途.这几天通过查询.学习.算是对defer有了一点浅显的认识. 1.什么是defer defer是go中一种延迟调用机制,defer后面的函数只有在当前函数执行完毕后才能执行,通常用于释放资源. 2.defer的特点 defer遵循先

随机推荐