MST

星途 面试题库

面试题:Go中context取消信号传递与资源释放的关系

在Go程序中,当通过context传递取消信号时,对于已打开的文件、数据库连接等资源,如何优雅地处理它们的释放?请结合代码和原理进行阐述。
26.2万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

原理

在Go中,context包用于在多个Go协程之间传递截止时间、取消信号和其他请求范围的值。当取消信号通过context传递时,我们需要在接收到该信号后,及时释放已打开的文件、数据库连接等资源,以避免资源泄漏。

代码示例

  1. 文件资源释放
package main

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

func readFile(ctx context.Context, filePath string) error {
    file, err := os.Open(filePath)
    if err != nil {
        return err
    }
    defer file.Close()

    ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()

    var data []byte
    var readErr error
    read := func() {
        data, readErr = ioutil.ReadAll(file)
    }

    go read()

    select {
    case <-ctx.Done():
        return ctx.Err()
    case <-time.After(3 * time.Second):
        // 模拟业务处理超时
        return fmt.Errorf("read operation timed out")
    }
    if readErr != nil {
        return readErr
    }
    fmt.Printf("File content: %s\n", data)
    return nil
}
  1. 数据库连接资源释放 假设使用database/sql包连接MySQL数据库(这里以MySQL为例,不同数据库驱动使用方式略有不同)
package main

import (
    "context"
    "database/sql"
    "fmt"
    _ "github.com/go - sql - driver/mysql"
    "time"
)

func queryDB(ctx context.Context, db *sql.DB) error {
    ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
    defer cancel()

    var result string
    err := db.QueryRowContext(ctx, "SELECT VERSION()").Scan(&result)
    if err != nil {
        return err
    }
    fmt.Printf("Database version: %s\n", result)
    return nil
}

在主函数中使用上述函数:

func main() {
    ctx := context.Background()

    // 文件操作
    err := readFile(ctx, "test.txt")
    if err != nil {
        fmt.Printf("Error reading file: %v\n", err)
    }

    // 数据库操作
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/test")
    if err != nil {
        fmt.Printf("Error opening database: %v\n", err)
        return
    }
    defer db.Close()

    err = queryDB(ctx, db)
    if err != nil {
        fmt.Printf("Error querying database: %v\n", err)
    }
}

总结

  1. 使用defer语句:对于文件资源,在打开文件后立即使用defer file.Close()来确保函数结束时文件会被关闭。这是Go语言中常用的资源清理方式,不管函数是正常结束还是因错误提前返回,defer语句都会执行。
  2. 结合contextWithTimeoutDone通道:通过context.WithTimeout设置操作的超时时间,并利用ctx.Done()通道来监听取消信号或超时信号。当接收到信号时,及时返回并进行相应的错误处理,避免无效的资源操作,确保资源能够在合理的时间内被释放。
  3. 数据库连接:使用database/sql包提供的QueryRowContext等带context参数的方法,在操作数据库时能够及时响应取消信号,并且在程序结束时关闭数据库连接(通过defer db.Close()),防止连接泄漏。