面试题答案
一键面试原理
在Go中,context
包用于在多个Go协程之间传递截止时间、取消信号和其他请求范围的值。当取消信号通过context
传递时,我们需要在接收到该信号后,及时释放已打开的文件、数据库连接等资源,以避免资源泄漏。
代码示例
- 文件资源释放
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
}
- 数据库连接资源释放
假设使用
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)
}
}
总结
- 使用
defer
语句:对于文件资源,在打开文件后立即使用defer file.Close()
来确保函数结束时文件会被关闭。这是Go语言中常用的资源清理方式,不管函数是正常结束还是因错误提前返回,defer
语句都会执行。 - 结合
context
的WithTimeout
和Done
通道:通过context.WithTimeout
设置操作的超时时间,并利用ctx.Done()
通道来监听取消信号或超时信号。当接收到信号时,及时返回并进行相应的错误处理,避免无效的资源操作,确保资源能够在合理的时间内被释放。 - 数据库连接:使用
database/sql
包提供的QueryRowContext
等带context
参数的方法,在操作数据库时能够及时响应取消信号,并且在程序结束时关闭数据库连接(通过defer db.Close()
),防止连接泄漏。