MST

星途 面试题库

面试题:Go的Context在并发任务链中的边界应对

假设有一个复杂的业务逻辑,由多个并发的Go协程组成任务链,前一个协程的输出作为后一个协程的输入。每个协程可能因为资源不足或超时等原因失败。请使用Context设计一个方案,确保在某个协程失败时,整个任务链能够有序地取消所有正在执行的协程,并处理好资源清理工作。详细描述你的设计思路,并给出关键代码示例。
45.9万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. Context 选择:使用 context.Context 作为控制协程生命周期的主要手段。context.WithCancel 函数创建一个可取消的上下文,当需要取消任务链时,调用取消函数,该上下文及其派生的上下文都会被取消。
  2. 传递 Context:在启动每个协程时,将 context.Context 作为参数传递进去。这样,一旦父上下文被取消,子协程能够感知并及时停止执行。
  3. 资源清理:每个协程在结束时负责清理自己所占用的资源。例如,关闭文件句柄、数据库连接等。

关键代码示例

package main

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

// 模拟一个任务函数,接受 context 和输入数据
func task(ctx context.Context, input int) (int, error) {
    // 模拟任务执行需要一些时间
    select {
    case <-time.After(2 * time.Second):
        // 这里可以添加实际的任务逻辑,返回结果或错误
        if input%2 == 0 {
            return input * 2, nil
        }
        return 0, fmt.Errorf("task failed for input %d", input)
    case <-ctx.Done():
        // 当 context 被取消时,返回取消错误
        return 0, ctx.Err()
    }
}

func main() {
    // 创建一个可取消的 context
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    // 模拟任务链的输入
    input := 3

    // 启动第一个协程
    var result int
    errChan := make(chan error, 1)
    go func() {
        var err error
        result, err = task(ctx, input)
        if err != nil {
            errChan <- err
            cancel() // 一旦某个任务失败,取消整个任务链
        }
        close(errChan)
    }()

    // 启动后续协程(这里假设只有一个后续协程作为示例)
    var finalResult int
    go func() {
        select {
        case err := <-errChan:
            if err != nil {
                fmt.Println("Error:", err)
                return
            }
        case <-ctx.Done():
            return
        }

        // 这里使用前一个任务的结果作为输入
        var err error
        finalResult, err = task(ctx, result)
        if err != nil {
            fmt.Println("Error in second task:", err)
        }
    }()

    // 等待所有协程完成
    time.Sleep(5 * time.Second)
    fmt.Println("Final Result:", finalResult)
}

在上述代码中:

  1. task 函数模拟一个具体的任务,它接受 context.Context 和输入数据。通过 select 语句监听 time.Afterctx.Done,当任务执行超时或者 context 被取消时,相应地返回错误。
  2. main 函数中,首先创建一个可取消的 context,然后启动第一个协程执行任务。如果第一个任务出错,通过 errChan 通知并调用 cancel 取消整个任务链。后续协程依赖前一个任务的结果,同样会监听 context 的取消信号,确保任务链有序取消,并在出错时进行相应处理。