MST

星途 面试题库

面试题:Go中管理goroutine生命周期时的资源释放问题

在Go语言里,假设一个goroutine负责打开文件并进行读写操作,当通过context取消该goroutine时,如何确保文件资源能够正确释放?请结合代码示例说明,同时阐述在复杂场景下,比如多个goroutine之间存在资源依赖时,如何处理资源释放以避免内存泄漏等问题。
34.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

通过context取消goroutine时确保文件资源正确释放

在Go语言中,可以使用context包来取消goroutine,同时使用defer语句来确保文件资源的正确释放。以下是示例代码:

package main

import (
    "context"
    "fmt"
    "io/ioutil"
    "os"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    go func(ctx context.Context) {
        file, err := os.Open("test.txt")
        if err != nil {
            fmt.Println("Failed to open file:", err)
            return
        }
        defer file.Close()

        select {
        case <-ctx.Done():
            fmt.Println("goroutine cancelled, closing file")
        default:
            data, err := ioutil.ReadAll(file)
            if err != nil {
                fmt.Println("Failed to read file:", err)
                return
            }
            fmt.Println("File content:", string(data))
        }
    }(ctx)

    time.Sleep(3 * time.Second)
}

在上述代码中:

  1. 使用context.WithTimeout创建一个带有超时的context,并在主函数结束时调用cancel函数。
  2. goroutine中打开文件后,立即使用defer file.Close()来确保无论goroutine如何结束(正常结束或因context取消而结束),文件都会被关闭。
  3. 使用select语句监听ctx.Done()通道,当接收到取消信号时,执行相应的清理操作(这里只是打印一条消息)。

复杂场景下处理资源释放以避免内存泄漏

当多个goroutine之间存在资源依赖时,处理资源释放变得更加复杂。一种常见的方法是使用sync.WaitGroup来等待所有相关的goroutine完成,并在主goroutine中进行统一的资源释放。

以下是一个简单示例,展示两个goroutine依赖同一个文件资源的情况:

package main

import (
    "context"
    "fmt"
    "io/ioutil"
    "os"
    "sync"
    "time"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    var wg sync.WaitGroup
    file, err := os.Open("test.txt")
    if err != nil {
        fmt.Println("Failed to open file:", err)
        return
    }
    defer file.Close()

    wg.Add(2)

    go func(ctx context.Context, file *os.File) {
        defer wg.Done()
        select {
        case <-ctx.Done():
            fmt.Println("goroutine 1 cancelled")
        default:
            data, err := ioutil.ReadAll(file)
            if err != nil {
                fmt.Println("Failed to read file in goroutine 1:", err)
                return
            }
            fmt.Println("File content in goroutine 1:", string(data))
        }
    }(ctx, file)

    go func(ctx context.Context, file *os.File) {
        defer wg.Done()
        select {
        case <-ctx.Done():
            fmt.Println("goroutine 2 cancelled")
        default:
            data, err := ioutil.ReadAll(file)
            if err != nil {
                fmt.Println("Failed to read file in goroutine 2:", err)
                return
            }
            fmt.Println("File content in goroutine 2:", string(data))
        }
    }(ctx, file)

    wg.Wait()
    fmt.Println("All goroutines completed, file will be closed automatically by defer")
}

在这个示例中:

  1. 使用sync.WaitGroup来等待两个goroutine完成。
  2. goroutine打开文件,并在函数结束时通过defer关闭文件。
  3. 每个goroutine在结束时调用wg.Done()来通知主goroutine自己已完成。
  4. 使用context来取消goroutine,并在goroutine中通过select监听ctx.Done()通道,以便在取消时进行相应的清理操作。

通过这种方式,可以有效地避免内存泄漏,并确保资源在所有相关goroutine结束后正确释放。