MST

星途 面试题库

面试题:Go 中 defer 与闭包结合使用的潜在问题

在 Go 语言中,将 defer 与闭包结合使用时可能会遇到哪些问题?请举例详细分析,并说明如何避免这些问题。
16.0万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

可能遇到的问题

  1. 延迟调用的变量捕获问题:defer 语句中的闭包会捕获其定义时的变量,而不是调用时的变量。 例如:
package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        defer func() {
            fmt.Println(i)
        }()
    }
}

上述代码期望输出 2 1 0,但实际输出是 3 3 3。原因是 defer 中的闭包捕获了循环变量 i,在 defer 语句执行时,i 的值已经是 3

  1. 资源释放顺序问题:当多个 defer 语句与闭包结合时,如果不注意,可能导致资源释放顺序不符合预期。 例如:
package main

import "fmt"

func main() {
    file1 := "file1"
    file2 := "file2"

    defer func() {
        fmt.Println("Close", file1)
    }()

    defer func() {
        fmt.Println("Close", file2)
    }()

    fmt.Println("Processing...")
}

这里虽然先 defer 关闭 file1,后 defer 关闭 file2,但实际输出是 Processing... Close file2 Close file1,因为 defer 是后进先出(LIFO)执行的。如果 file2 的关闭依赖 file1,就可能出问题。

避免方法

  1. 解决延迟调用的变量捕获问题
    • 传值方式:将需要的变量以参数形式传入闭包。
package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        defer func(j int) {
            fmt.Println(j)
        }(i)
    }
}

这样会输出 2 1 0,因为每次迭代都创建了一个新的闭包,并传入了当时 i 的值。 - 使用短变量声明:在 defer 内部使用短变量声明 := 重新定义变量。

package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        defer func() {
            j := i
            fmt.Println(j)
        }()
    }
}

同样会输出 2 1 0,通过在闭包内重新定义变量,避免了对外部循环变量的捕获。

  1. 解决资源释放顺序问题
    • 调整 defer 语句顺序:根据资源依赖关系,调整 defer 语句的书写顺序,使依赖关系正确。如果 file2 依赖 file1,则先 defer 关闭 file2,后 defer 关闭 file1
package main

import "fmt"

func main() {
    file1 := "file1"
    file2 := "file2"

    defer func() {
        fmt.Println("Close", file1)
    }()

    defer func() {
        fmt.Println("Close", file2)
    }()

    fmt.Println("Processing...")
}
- **使用显式的释放函数调用**:不依赖 defer 的 LIFO 特性,在函数结束时显式调用资源释放函数,并按照正确顺序进行调用。
package main

import "fmt"

func closeFile1() {
    fmt.Println("Close file1")
}

func closeFile2() {
    fmt.Println("Close file2")
}

func main() {
    fmt.Println("Processing...")
    closeFile2()
    closeFile1()
}