问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

在Go中使用defer:最佳实践和常见用例

创作时间:
作者:
@小白创作中心

在Go中使用defer:最佳实践和常见用例

引用
1
来源
1.
https://m.php.cn/faq/954069.html

在Go语言中,defer关键字是一个强大的工具,可以帮助管理资源并确保函数退出时执行清理操作。延迟函数在周围函数返回时执行,无论它正常返回、由于错误还是由于恐慌。这可以确保无论函数如何退出,清理代码都会运行,使资源管理更简单、更可靠。

关于延期的要点:

  • 执行时机:当周围函数返回时,延迟函数按照LIFO(后进先出)顺序执行,无论是完成执行、遇到return语句还是由于恐慌。
  • 资源管理:它有助于自动关闭文件和网络连接等资源、解锁互斥体以及执行其他清理任务。

目录

  1. 多个defer语句的顺序
  2. 资源清理
  3. 解锁互斥体
  4. 释放数据库连接
  5. 恢复状态
  6. 处理恐慌
  7. 记录和计时
  8. 刷新缓冲区
  9. 处理http请求体
  10. 不延迟关闭失败的开口的风险
  11. 在检查文件是否打开之前使用defer的风险
  12. 结论

1. 多个defer语句的顺序

在Go中,函数内的多个defer语句按照reverse出现的顺序执行。这对于管理多个清理任务非常有用,确保它们在函数退出时按特定顺序执行。

func examplefunction() {
    fmt.Println("start of function")
    defer fmt.Println("first defer: executed last")
    defer fmt.Println("second defer: executed second")
    defer fmt.Println("third defer: executed first")
    fmt.Println("end of function")
}

输出:

start of function
end of function
third defer: executed first
second defer: executed second
first defer: executed last

2. 资源清理

defer最常见的用途之一是确保文件等资源在不再需要后正确关闭。

func processfile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err // return the error if opening the file fails
    }
    defer file.Close() // ensure the file is closed when the function exits
    // process the file...
    return nil
}

os.File实现了io.ReadCloser,所以这里使用defer可以确保文件正确关闭,防止资源泄漏。

3. 解锁互斥体

处理并发时,释放锁以防止死锁至关重要。defer有助于有效管理互斥体。

var mu sync.Mutex
func criticalsection() {
    mu.Lock()
    defer mu.Unlock() // ensure the mutex is unlocked when the function exits
    // critical section...
}

通过推迟mu.Unlock(),可以确保互斥锁始终被释放,从而使代码更易于理解且不易出错。

4. 释放数据库连接

不再需要数据库连接时应关闭以释放资源

func querydatabase() error {
    db, err := sql.Open("driver", "database=example")
    if err != nil {
        return err
    }
    defer db.Close() // ensure the database connection is closed when the function exits
    // query the database...
    return nil
}

5. 恢复状态

  • 示例:更改和恢复工作目录

更改工作目录时,将其恢复到原始状态很重要

func changedirectory() error {
    olddir, err := os.Getwd()
    if err != nil {
        return err
    }
    err = os.Chdir("/tmp")
    if err != nil {
        return err
    }
    defer os.Chdir(olddir) // restore the working directory when the function exits
    // work in /tmp...
    return nil
}

使用defer可以轻松自动恢复原目录

6. 处理恐慌

  • 示例:从恐慌中恢复

defer可用于从恐慌中恢复并优雅地处理错误。

func safefunction() {
    defer func() {
        if r := recover(); r != nil {
            log.Println("recovered from panic:", r)
        }
    }()
    // code that might panic...
}

通过推迟处理恐慌的函数,您可以确保您的应用程序即使在遇到意外错误时也保持稳健。

7. 记录和计时

  • 示例:定时函数执行

defer对于测量执行时间或在函数退出时进行记录非常有用。

func measuretime() {
    start := time.Now()
    defer func() {
        duration := time.Since(start)
        log.Printf("execution time: %v", duration)
    }()
    // code to measure...
}

这种方法简化了计时代码并确保在函数完成时记录持续时间。

8. 冲洗缓冲器

  • 示例:刷新缓冲I/O

缓冲的I/O操作应该被刷新以确保所有数据都被写出。

func bufferedwrite() {
    buf := bufio.NewWriter(os.Stdout)
    defer buf.Flush() // ensure the buffer is flushed when the function exits
    buf.WriteString("hello, world!")
}

这里使用defer可以保证所有缓冲的数据在函数完成之前被写出。

9. 处理http请求体

  • 示例:关闭http请求体

http请求体实现了io.ReadCloser,因此使用后关闭它们以释放资源并避免泄漏至关重要。

func handlerequest(req *http.Request) error {
    // ensure that the request body is closed when the function exits
    defer func() {
        if err := req.Body.Close(); err != nil {
            log.Println("error closing request body:", err)
        }
    }()
    body, err := io.ReadAll(req.Body)
    if err != nil {
        return err
    }
    // process the body...
    fmt.Println("request body:", string(body))
    return nil
}

通过推迟req.Body.Close(),您可以确保主体正确关闭,即使在读取或处理主体时发生错误也是如此。

10. 不延期关闭失败的开仓的风险

当您在Go中打开文件或其他资源时,确保不再需要该资源时正确关闭该资源至关重要。但是,如果您在错误检查后尝试关闭资源而不使用defer,则可能会给您的代码带来风险。

  • 没有延迟的示例
file, err := os.Open(filename)
if err != nil {
    return err // handle error
}
// risk: if something goes wrong before this point, the file might never be closed
// additional operations here...
file.Close() // attempt to close the file later

在Go中不使用defer关闭资源可能会导致意想不到的后果,例如尝试关闭从未成功打开的资源,从而导致意外行为或恐慌。此外,如果在显式close()调用之前发生错误,资源可能会保持打开状态,从而导致泄漏并耗尽系统资源。随着代码变得越来越复杂,确保所有资源正确关闭变得越来越困难,从而增加了忽略关闭操作的可能性。

11. 在检查文件是否打开之前使用defer的风险

在Go中,在验证资源(如文件)已成功打开后放置defer语句至关重要。在错误检查之前放置延迟可能会带来一些风险和不良行为。

错误使用示例

file, err := os.Open(fileName)
defer file.Close() // Incorrect: This should be deferred after the error check
if err != nil {
    return err // Handle error
}
// Additional operations here...

在检查os.Open是否成功之前放置defer file.Close()可能导致几个问题。如果文件未打开并且为nil,尝试关闭它会导致运行时恐慌,因为即使发生错误,Go也会执行所有延迟函数。这种方法还会使代码产生误导,暗示文件已成功打开,而实际上它可能没有打开,这使理解和维护变得复杂。此外,如果确实发生恐慌,调试就会变得更具挑战性,尤其是在复杂的代码库中,因为将问题追溯到错误的延迟可能需要额外的努力。

12. 结论

Go中的defer关键字通过确保函数退出时自动执行清理操作来简化资源管理并增强代码清晰度。通过在这些常见场景中使用defer,您可以编写更健壮、可维护且无错误的代码。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号