MST
星途 面试题库

面试题:Go语言context在复杂并发场景下的应用

在一个微服务架构中,一个请求可能会触发多个子任务并发执行,每个子任务又可能会启动更多的子goroutine。这些任务之间存在依赖关系,例如任务A的结果可能作为任务B和任务C的输入。同时,整个请求有一个总的超时时间。请设计一个使用context来管理这种复杂并发场景的方案,确保在总超时时间内,如果某个子任务失败,其他相关任务能够被及时取消,并且能够正确处理任务之间的依赖关系。请详细描述设计思路并给出关键代码片段。
42.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 创建根上下文:使用 context.WithTimeout 创建带有总超时时间的根上下文,这将作为整个请求的上下文,控制所有子任务的执行时间。
  2. 任务依赖处理:对于有依赖关系的任务,将前序任务的结果传递给后续任务。例如,任务A完成后,将其结果作为参数传递给任务B和任务C。
  3. 子任务并发执行:利用 sync.WaitGroup 来等待所有子任务完成。每个子任务在一个单独的goroutine中执行,并且将根上下文或基于根上下文衍生的子上下文传递给每个子任务。
  4. 错误处理与取消:当某个子任务失败时,通过上下文的取消机制通知其他相关任务取消执行。每个子任务在执行过程中需要定期检查上下文的取消信号。

关键代码片段

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

// 模拟任务A
func taskA(ctx context.Context) (string, error) {
    select {
    case <-ctx.Done():
        return "", ctx.Err()
    case <-time.After(2 * time.Second):
        return "result of A", nil
    }
}

// 模拟任务B,依赖任务A的结果
func taskB(ctx context.Context, aResult string) (string, error) {
    select {
    case <-ctx.Done():
        return "", ctx.Err()
    case <-time.After(2 * time.Second):
        return "result of B based on " + aResult, nil
    }
}

// 模拟任务C,依赖任务A的结果
func taskC(ctx context.Context, aResult string) (string, error) {
    select {
    case <-ctx.Done():
        return "", ctx.Err()
    case <-time.After(2 * time.Second):
        return "result of C based on " + aResult, nil
    }
}

func main() {
    var wg sync.WaitGroup
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    // 执行任务A
    var aResult string
    var aErr error
    wg.Add(1)
    go func() {
        defer wg.Done()
        aResult, aErr = taskA(ctx)
    }()

    // 等待任务A完成
    wg.Wait()
    if aErr != nil {
        fmt.Println("task A failed:", aErr)
        return
    }

    // 执行任务B和C
    var bResult string
    var bErr error
    var cResult string
    var cErr error

    wg.Add(2)
    ctxB, cancelB := context.WithCancel(ctx)
    ctxC, cancelC := context.WithCancel(ctx)
    defer cancelB()
    defer cancelC()

    go func() {
        defer wg.Done()
        bResult, bErr = taskB(ctxB, aResult)
    }()

    go func() {
        defer wg.Done()
        cResult, cErr = taskC(ctxC, aResult)
    }()

    // 等待任务B和C完成
    wg.Wait()

    if bErr != nil {
        fmt.Println("task B failed:", bErr)
        cancelC()
    }
    if cErr != nil {
        fmt.Println("task C failed:", cErr)
        cancelB()
    }

    fmt.Println("task A result:", aResult)
    fmt.Println("task B result:", bResult)
    fmt.Println("task C result:", cResult)
}

代码说明

  1. 任务函数taskAtaskBtaskC 分别模拟不同的任务,taskBtaskC 依赖 taskA 的结果。
  2. 主函数
    • 创建带有超时的根上下文 ctx 和取消函数 cancel
    • 并发执行 taskA,并等待其完成。
    • 根据 taskA 的结果,并发执行 taskBtaskC,同时为它们创建可取消的子上下文。
    • 等待 taskBtaskC 完成,若其中一个失败,则取消另一个任务。
    • 最后输出各个任务的结果或错误信息。