MST

星途 面试题库

面试题:Go语言中Context的CancelFunc在Goroutine超时控制中的细节

假设在一个复杂的多Goroutine协作场景中,有多个Goroutine依赖同一个Context进行超时控制。当主Goroutine调用CancelFunc取消Context后,其他Goroutine是如何感知到并进行相应处理的?具体说明Goroutine内部对Context.Done()通道的监听机制以及资源清理的最佳实践。
14.9万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 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:
                // 正常业务逻辑
            }
        }
    }
    
  2. Goroutine内部对Context.Done()通道的监听机制
    • Go语言的运行时会负责管理Context.Done()通道的关闭操作。当CancelFunc被调用时,会触发一系列的内部操作,最终关闭Context.Done()通道。
    • 监听Context.Done()通道的Goroutine在通道关闭时,会从select语句中相应的case分支继续执行,从而感知到Context的取消。
  3. 资源清理的最佳实践
    • 文件资源:如果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.ClientCancelRequest方法(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.WaitGroupdefer:可以使用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()
    }