编程 Golang 中应该知道的 defer 知识

2024-11-18 13:18:56 +0800 CST views 628

Golang 中你应该知道的 defer 知识(基础应用篇)

1.1 使用场景

资源清理

例如关闭文件、数据库连接或网络连接等。

func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()
    // do something
}

解锁资源

在并发编程中确保锁被解锁、执行 wg.Done 等。

var mu sync.Mutex

func criticalSection() {
    mu.Lock()
    defer mu.Unlock()
    // do something
}

func worker(wg *sync.WaitGroup, id int) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
}

确保函数执行结束后进行后续操作

如记录日志或调试信息、性能分析等。

package main

import (
    "fmt"
    "time"
)

// 记录函数执行时间
func timeTrack(start time.Time, name string) {
    elapsed := time.Since(start)
    fmt.Printf("%s took %s\n", name, elapsed)
}

// 记录并跟踪函数的执行
func someFunction() {
    defer timeTrack(time.Now(), "someFunction")
    defer fmt.Println("Function completed")
    
    fmt.Println("Executing someFunction...")
    time.Sleep(2 * time.Second)
    
    // 模拟多个返回点
    if true {
        return // 即使有 return,defer 仍然会执行
    }
    
    fmt.Println("This line won't be reached")
}

func main() {
    someFunction()
}

通过 defer,我们可以极大简化代码的编写和维护,减少资源泄露和逻辑错误的风险。接下来,我们深入了解 defer 的一些核心概念。

1.2 触发时机

defer 语句会在包围它的函数返回之前执行。无论函数怎样退出(正常返回或是 panic),只要在 returnpanic 前声明了 defer,其语句的执行都将得到保证。

func deferFn1() {
    defer fmt.Println("defer")
    fmt.Println("func")
}
// output:
// func
// defer

func deferFn2() {
    defer fmt.Println("defer")
    return
}
// output:
// defer

func deferFn3() {
    defer fmt.Println("defer")
    panic("panic")
}
// output:
// defer
// panic stack

1.3 执行顺序与使用须知

Go 语言中的 defer 语句遵循栈式调用(LIFO - Last In, First Out),即最后一个声明的 defer 最先执行。

func main() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    fmt.Println("end...")
}
// output:
// end...
// 3
// 2
// 1

此外,defer 的参数会在声明时立即求值,而不是在延迟函数实际执行时求值。这意味着当 defer 语句执行时,已经保存的参数值将被使用。

func main() {
    startedAt := time.Now()
    defer func() {
        fmt.Println(time.Since(startedAt))
    }()
    time.Sleep(time.Second)
}
// output: 1s

1.4 返回值处理

在函数有显式 return 语句时,defer 会在返回值被设置之后执行。特别地,如果函数有命名返回参数,defer 可以修改这些返回参数。

func deferReturnFn() int {
    a := 1
    defer func() {
        a = 2
    }()
    return a
}
// output: 1

在命名返回参数的情况下:

func deferReturnFn() (res int) {
    res = 1
    defer func() {
        res = 2
    }()
    return
}
// output: 2

匿名函数的返回值会被丢弃:

func deferReturnFn() int {
    res := 1
    defer func() int {
        res++
        return res
    }()
    return res
}
// output: 1

1.5 使用注意点

  • 栈式调用defer 语句的执行顺序是后进先出。
  • 匿名函数defer 可以使用闭包捕获外部变量。
  • 返回值处理defer 可以修改命名返回参数。

此外,要注意 defer 的位置:

// good
func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()
    // do something
}

// bad
func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        log.Fatal(err)
    }
    // do something
    defer file.Close()
}

1.6 哲学思想

defer 设计背后的哲学思想可以从资源清理的角度理解。传统的资源清理方法通常会使用 goto 或者类似的手段处理多个资源的释放,而 defer 的引入简化了这一过程,让代码更加简洁可读。通过 defer,Go 语言继承了 RAII 的设计思想,减少了程序员的心智负担。

1.7 总结

本文介绍了 defer 的基础应用,从使用场景、执行顺序到返回值处理等多个方面进行了详细探讨。defer 是一个非常强大的关键字,掌握好它可以让我们写出更加优雅和可靠的 Go 代码。

进一步思考:

  • defer 的性能如何?Go 团队做了哪些优化?
  • defer 的设计哲学从源码层面是如何实现的?

这些问题值得深入研究,希望在未来能有机会探讨其中的奥秘。

参考链接

  1. Go 语言规范 - Defer Statements
  2. 11 | 程序中的错误处理:错误返回码和异常捕捉 - 左耳听风 - 极客时间
复制全文 生成海报 编程 Go语言 软件开发 技术知识

推荐文章

一个有趣的进度条
2024-11-19 09:56:04 +0800 CST
Claude:审美炸裂的网页生成工具
2024-11-19 09:38:41 +0800 CST
PHP 命令行模式后台执行指南
2025-05-14 10:05:31 +0800 CST
Redis函数在PHP中的使用方法
2024-11-19 04:42:21 +0800 CST
robots.txt 的写法及用法
2024-11-19 01:44:21 +0800 CST
关于 `nohup` 和 `&` 的使用说明
2024-11-19 08:49:44 +0800 CST
PHP服务器直传阿里云OSS
2024-11-18 19:04:44 +0800 CST
Vue3中如何处理异步操作?
2024-11-19 04:06:07 +0800 CST
Go语言中的mysql数据库操作指南
2024-11-19 03:00:22 +0800 CST
快手小程序商城系统
2024-11-25 13:39:46 +0800 CST
如何在 Vue 3 中使用 TypeScript?
2024-11-18 22:30:18 +0800 CST
mysql关于在使用中的解决方法
2024-11-18 10:18:16 +0800 CST
15 个你应该了解的有用 CSS 属性
2024-11-18 15:24:50 +0800 CST
Go 单元测试
2024-11-18 19:21:56 +0800 CST
Vue中的异步更新是如何实现的?
2024-11-18 19:24:29 +0800 CST
Vue3中如何实现响应式数据?
2024-11-18 10:15:48 +0800 CST
使用 Go Embed
2024-11-19 02:54:20 +0800 CST
平面设计常用尺寸
2024-11-19 02:20:22 +0800 CST
程序员茄子在线接单