MST

星途 面试题库

面试题:Go的defer在并发场景下的特性与陷阱

在Go语言的并发编程场景中,defer语句会有哪些特性与潜在的陷阱?请举例说明如何避免这些陷阱,比如在使用goroutine进行资源管理时,defer的正确使用方式。
31.8万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

defer语句特性

  1. 延迟执行:defer语句后的函数会在包含该defer语句的函数返回前执行。例如:
package main

import "fmt"

func main() {
    fmt.Println("开始")
    defer fmt.Println("结束")
}

执行结果会先输出“开始”,最后输出“结束”。 2. 按后进先出(LIFO)顺序执行:如果函数中有多个defer语句,它们会按照后进先出的顺序执行。例如:

package main

import "fmt"

func main() {
    defer fmt.Println("defer 1")
    defer fmt.Println("defer 2")
    fmt.Println("main")
}

输出结果为:

main
defer 2
defer 1
  1. 可用于资源清理:常用于关闭文件、数据库连接等资源的清理。例如:
package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("test.txt")
    if err != nil {
        fmt.Println("打开文件错误:", err)
        return
    }
    defer file.Close()
    // 后续处理文件操作
}

潜在陷阱

  1. 闭包引用问题:defer语句中的函数如果使用了闭包,要注意闭包中变量的值变化。例如:
package main

import "fmt"

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

预期输出可能是0, 1, 2,但实际输出是3, 3, 3。这是因为闭包引用的是循环变量i,当defer函数执行时,i已经变为3。 2. goroutine中资源管理不当:如果在goroutine中使用defer进行资源管理,可能因为goroutine提前退出导致资源未正确清理。例如:

package main

import (
    "fmt"
    "os"
)

func worker() {
    file, err := os.Open("test.txt")
    if err != nil {
        fmt.Println("打开文件错误:", err)
        return
    }
    defer file.Close()
    // 假设这里有一个可能导致goroutine提前退出的操作
    panic("模拟错误")
    // 后续文件操作代码
}

func main() {
    go worker()
    select {}
}

在这个例子中,由于panic导致goroutine提前退出,文件可能未正确关闭。

避免陷阱的方法

  1. 解决闭包引用问题:通过传值方式避免引用同一变量。例如:
package main

import "fmt"

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

这样每次循环都会创建一个新的index变量,确保defer函数引用的是正确的值,输出为0, 1, 2。 2. 正确处理goroutine中的资源管理:使用recover来捕获panic并正确清理资源。例如:

package main

import (
    "fmt"
    "os"
)

func worker() {
    file, err := os.Open("test.txt")
    if err != nil {
        fmt.Println("打开文件错误:", err)
        return
    }
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获到panic:", r)
            file.Close()
        }
    }()
    panic("模拟错误")
}

func main() {
    go worker()
    select {}
}

通过recover捕获panic,并在defer中确保文件被正确关闭。