golang中的defer函数怎么用

2024-03-14,

本文小编为大家详细介绍“golang中的defer函数怎么用”,内容详细,步骤清晰,细节处理妥当,希望这篇“golang中的defer函数怎么用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    golang的defer

    什么是defer

    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是否未处理的情况。
    所以一般情况下,我们程序需要捕获这种异常中断,在程序退出前,手动做一些处理。

    读到这里,这篇“golang中的defer函数怎么用”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注本站行业资讯频道。

    《golang中的defer函数怎么用.doc》

    下载本文的Word格式文档,以方便收藏与打印。