MST

星途 面试题库

面试题:Go语言中context取消机制在复杂业务场景的应用

在一个涉及多个步骤且步骤间存在依赖关系的复杂业务场景中,例如一个包含数据库查询、网络请求和本地文件操作的业务流程,每个操作都在独立的goroutine中执行,如何合理利用context的取消机制,确保在任意一个步骤出现错误或外部主动取消请求时,所有相关的goroutine都能及时停止,同时保证资源的正确释放,请详细阐述设计思路并给出关键代码片段。
28.3万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 创建 context:使用 context.WithCancel 创建一个可取消的 context,这个 context 会在所有相关 goroutine 之间传递。
  2. 传递 context:将创建的 context 传递给每个执行独立操作(数据库查询、网络请求、本地文件操作)的 goroutine。
  3. 监听取消信号:在每个 goroutine 中,通过 ctx.Done() 通道监听取消信号。一旦接收到取消信号,立即停止当前操作并进行资源清理。
  4. 错误处理与取消:当任意一个操作出现错误时,调用 cancel 函数取消 context,从而通知其他 goroutine 停止。

关键代码片段

package main

import (
    "context"
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)

// 模拟数据库查询
func dbQuery(ctx context.Context) error {
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        // 模拟数据库查询操作
        time.Sleep(2 * time.Second)
        return nil
    }
}

// 模拟网络请求
func networkRequest(ctx context.Context) error {
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        req, err := http.NewRequestWithContext(ctx, "GET", "http://example.com", nil)
        if err != nil {
            return err
        }
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return err
        }
        defer resp.Body.Close()
        return nil
    }
}

// 模拟本地文件操作
func localFileOperation(ctx context.Context) error {
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        // 模拟本地文件操作
        _, err := ioutil.ReadFile("example.txt")
        return err
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    var errs []error

    // 启动数据库查询 goroutine
    go func() {
        if err := dbQuery(ctx); err != nil {
            cancel()
            errs = append(errs, err)
        }
    }()

    // 启动网络请求 goroutine
    go func() {
        if err := networkRequest(ctx); err != nil {
            cancel()
            errs = append(errs, err)
        }
    }()

    // 启动本地文件操作 goroutine
    go func() {
        if err := localFileOperation(ctx); err != nil {
            cancel()
            errs = append(errs, err)
        }
    }()

    // 等待一段时间,确保所有 goroutine 有机会执行
    time.Sleep(5 * time.Second)

    if len(errs) > 0 {
        for _, err := range errs {
            fmt.Println("Error:", err)
        }
    } else {
        fmt.Println("All operations completed successfully")
    }
}

在上述代码中:

  1. 每个操作函数(dbQuerynetworkRequestlocalFileOperation)都通过 ctx.Done() 监听取消信号。
  2. 当任意一个操作出现错误时,调用 cancel() 取消 context,从而通知其他 goroutine 停止。
  3. main 函数中启动了三个独立的 goroutine 分别执行不同的操作,并等待一段时间后检查是否有错误发生。