go语言defer使用

Go语言defer详解

defer

Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的:

func ReadWrite() bool {
    file.Open("file")
// 做一些工作
    if failureX {
        file.Close()
        return false
    }

    if failureY {
        file.Close()
        return false
    }

    file.Close()
    return true
}

我们看到上面有很多重复的代码,Go的defer有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在defer后指定的函数会在函数退出前调用。

func ReadWrite() bool {
    file.Open("file")
    defer file.Close()
    if failureX {
        return false
    }
    if failureY {
        return false
    }
    return true
}

如果有很多调用defer,那么defer是采用后进先出模式,所以如下代码会输出4 3 2 1 0

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}


defer 给我的第一印象就是,类似java中的

try {

}finally {

}

我目前的理解就是,在函数块中使用defer,就是函数对应的有一个栈空间,先进后出。需要函数结束后调用栈,来出发defer操作。

如果,一个对象的创建,很消耗内存,需要及时关闭,defer无法像try finnaly哪样准确。

package main

import "fmt"
import "time"

type User struct {
        username string
}

func (this *User) Close() {
        fmt.Println(this.username, "Closed !!!")
}

func main() {
        u1 := &User{"jack"}
        defer u1.Close()
        u2 := &User{"lily"}
        defer u2.Close()

        time.Sleep(10 * time.Second)

        fmt.Println("Done !")

}
[vicky@localhost goroutine]$


[vicky@localhost goroutine]$ go run deferTest1.go
Done !
lily Closed !!!
jack Closed !!!
[vicky@localhost goroutine]$


实际上,线程Sleep的10秒,u1,和u2早就可以Close()了,但却需要依赖main()函数的结束,才能defer执行。

那么尝试给defer添加内部代码区:


package main

import "fmt"
import "time"

type User struct {
        username string
}

func (this *User) Close() {
        fmt.Println(this.username, "Closed !!!")
}

func main() {
        {
                // 即便加了代码快范围,依旧也要主函数体结束才执行defer
                u1 := &User{"jack"}
                defer u1.Close()
        }
        u2 := &User{"lily"}
        defer u2.Close()

        time.Sleep(10 * time.Second)

        fmt.Println("Done !")

}

Done !
lily Closed !!!
jack Closed !!!
[vicky@localhost goroutine]$


依旧defer的执行在Done!后。那么如何才能达到try finally 哪样准确的Close呢?

package main

import "fmt"
import "time"

type User struct {
        username string
}

func (this *User) Close() {
        fmt.Println(this.username, "Closed !!!")
}

func main() {
        u1 := &User{"jack"}
        f(u1) // 这样的方式,u1才会不依赖main函数的执行

        // 这样的方式,u2也不会依赖main函数的执行
        u2 := &User{"lily"}
        // m := func() {
        //         defer u2.Close()
        //         // u2 do somthing
        // }
        // m()
        func() {
                 defer u2.Close()
                 // u2 do somthing
        }()
time.Sleep(10 * time.Second) fmt.Println("Done !")}func f(u *User) { defer u.Close() // u1 do gomething}
[vicky@localhost goroutine]$ go run deferTest3.go
jack Closed !!!
lily Closed !!!
Done !


这样的使用方式,视乎不太合理,但却有存在的必要性。大多数情况下,可以用于 u1,u2  之类非常消耗内存,或者cpu,其后执行时间过程且没有太多关联的情况。既保留了defer的功能特性,也满足范围精确控制的条件!




<think>好的,我现在需要处理用户关于Go语言defer关键字的使用方法的查询。用户可能已经看过一些资料,但想要更详细的教程。首先,我应该回顾defer的基本概念和常见用途,确保覆盖正确的语法和最佳实践。 根据引用资料,特别是引用[1]和[5],defer主要用于资源释放、异常处理和并发编程。我需要强调这些应用场景,并提供示例代码。同时,引用[2]提到不要在循环中使用defer,或者对命名返回值进行操作,这可能导致意外结果,这点必须重点指出。 接下来,用户可能需要知道defer的执行顺序,比如后进先出(LIFO),这点在引用[4]中有所涉及。另外,引用[3]提到defer的原理,虽然用户没有直接问原理,但简单提及可能有帮助。 需要注意的常见错误,如延迟调用含有闭包的函数时的变量捕获问题,以及性能方面的考虑。引用[1]提到性能敏感场景要谨慎使用,这可能涉及到资源释放的必要性与性能之间的权衡。 还要检查是否有其他注意事项,比如错误处理语句的位置,引用[2]建议将defer放在错误检查之后,避免panic。此外,命名返回值的处理,如引用[4]中的执行顺序影响,需要举例说明。 最后,用户可能需要相关的问题来进一步学习,比如defer的执行时机、与匿名函数结合使用的效果,以及如何避免资源泄漏。这些问题需要涵盖常见的疑惑点,帮助用户深入理解。</think>以下为Go语言中`defer`关键字的详细使用教程,结合实践场景和注意事项: --- ### 一、`defer`的基本作用 `defer`用于延迟函数或方法的执行,**在所属函数返回前按后进先出(LIFO)顺序执行**[^4]。典型应用场景包括: 1. 资源释放(文件关闭、锁释放) 2. 异常处理(`recover`与`panic`配合) 3. 日志记录和状态清理 示例: ```go func readFile(filename string) { file, _ := os.Open(filename) defer file.Close() // 确保文件在函数返回前关闭 // 文件操作... } ``` --- ### 二、核心使用规则 1. **执行顺序** 多个`defer`按声明倒序执行: ```go defer fmt.Print("A") // 第3执行 defer fmt.Print("B") // 第2执行 defer fmt.Print("C") // 第1执行 ``` 输出:`CBA` 2. **参数求值时机** `defer`的参数在声明时立即求值,而非执行时: ```go x := 1 defer fmt.Println(x) // 输出1(x的当前值) x = 2 ``` 3. **返回值陷阱** 当函数使用命名返回值时,`defer`可以修改返回值: ```go func demo() (result int) { defer func() { result++ }() // 最终返回1 return 0 } ``` --- ### 三、高级使用技巧 1. **错误恢复** 配合`recover`实现异常捕获: ```go func safeOperation() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered:", r) } }() // 可能触发panic的代码... } ``` 2. **耗时监控** 记录函数执行时间: ```go func trackTime() { start := time.Now() defer func() { fmt.Println("耗时:", time.Since(start)) }() // 业务逻辑... } ``` 3. **资源清理链** 多个资源的有序释放: ```go func processDB() { conn := connectDB() defer conn.Close() tx := conn.Begin() defer tx.Rollback() // 默认回滚,若成功再提交 // 事务操作... tx.Commit() } ``` --- ### 四、需要避免的陷阱 1. **循环中的`defer`** 错误用法: ```go for _, file := range files { f, _ := os.Open(file) defer f.Close() // 可能导致文件描述符泄漏 } ``` 正确做法:将循环体封装为函数 ```go for _, file := range files { func(f string) { file, _ := os.Open(f) defer file.Close() // 操作文件... }(file) } ``` 2. **闭包变量捕获** 延迟函数捕获的是变量的最终值: ```go for i := 0; i < 3; i++ { defer func() { fmt.Print(i) }() // 输出333 } ``` 解决方法:通过参数传递当前值 ```go for i := 0; i < 3; i++ { defer func(n int) { fmt.Print(n) }(i) // 输出210 } ``` 3. **性能敏感场景** `defer`会产生额外的函数调用开销,在关键路径代码中可考虑直接调用清理函数。 --- ### 五、最佳实践原则 1. 将`defer`紧跟在资源获取语句后,增强可读性 2. 避免在`defer`中进行复杂逻辑操作 3. 对需要错误处理的函数,先进行错误检查再使用`defer`[^2] 4. 优先用于必须执行的清理操作(如解锁、关闭连接) ---
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值