面试题答案
一键面试- Goroutine感知Context取消的方式:
- 每个依赖该Context的Goroutine通过监听
Context.Done()
通道来感知Context的取消。当主Goroutine调用CancelFunc
取消Context时,Context.Done()
通道会被关闭。 - 在Goroutine代码中,一般使用
select
语句监听Context.Done()
通道,例如:
func myGoroutine(ctx context.Context) { for { select { case <-ctx.Done(): // 处理Context取消的逻辑 return default: // 正常业务逻辑 } } }
- 每个依赖该Context的Goroutine通过监听
- Goroutine内部对Context.Done()通道的监听机制:
- Go语言的运行时会负责管理
Context.Done()
通道的关闭操作。当CancelFunc
被调用时,会触发一系列的内部操作,最终关闭Context.Done()
通道。 - 监听
Context.Done()
通道的Goroutine在通道关闭时,会从select
语句中相应的case
分支继续执行,从而感知到Context的取消。
- Go语言的运行时会负责管理
- 资源清理的最佳实践:
- 文件资源:如果Goroutine打开了文件,在感知到Context取消后,要及时关闭文件。例如:
func myGoroutine(ctx context.Context) { file, err := os.Open("test.txt") if err != nil { // 处理文件打开错误 return } defer file.Close() for { select { case <-ctx.Done(): // 清理可能的临时文件等 return default: // 正常读取文件逻辑 } } }
- 网络连接:如果Goroutine建立了网络连接(如TCP连接、HTTP连接等),在Context取消时要关闭连接。对于HTTP客户端请求,可以使用
http.Client
的CancelRequest
方法(Go 1.13+)。例如:
func myGoroutine(ctx context.Context) { client := &http.Client{} req, err := http.NewRequestWithContext(ctx, "GET", "http://example.com", nil) if err != nil { // 处理请求创建错误 return } resp, err := client.Do(req) if err != nil { // 处理请求执行错误 return } defer resp.Body.Close() select { case <-ctx.Done(): // 关闭连接等清理操作 return default: // 处理响应逻辑 } }
- 数据库连接:如果Goroutine使用了数据库连接(如SQL数据库),在Context取消时,要确保正确关闭连接或回滚事务(如果有未提交事务)。例如,对于
database/sql
包:
func myGoroutine(ctx context.Context) { db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/test") if err != nil { // 处理数据库连接错误 return } defer db.Close() tx, err := db.BeginTx(ctx, nil) if err != nil { // 处理事务开始错误 return } select { case <-ctx.Done(): tx.Rollback() return default: // 执行SQL操作并提交事务 tx.Commit() } }
- 使用
sync.WaitGroup
和defer
:可以使用sync.WaitGroup
来等待所有相关Goroutine完成资源清理并退出。在主Goroutine中,启动多个Goroutine时,为每个Goroutine的WaitGroup
计数加1,Goroutine结束时(包括因Context取消而结束),调用WaitGroup.Done()
。主Goroutine通过WaitGroup.Wait()
等待所有Goroutine完成清理。例如:
func main() { var wg sync.WaitGroup ctx, cancel := context.WithCancel(context.Background()) for i := 0; i < 5; i++ { wg.Add(1) go func(ctx context.Context) { defer wg.Done() myGoroutine(ctx) }(ctx) } // 一段时间后取消Context time.Sleep(time.Second) cancel() wg.Wait() }