MST

星途 面试题库

面试题:Go中复杂场景下select语句的超时控制与错误处理

在一个高并发的Go程序中,有多个不同类型的任务(如数据库查询、文件读取、网络请求)需要并发执行,每个任务都有自己独立的超时时间,并且每个任务执行过程中可能会出现不同类型的错误(如数据库连接错误、文件不存在错误、网络连接超时等)。请设计一个通用的框架来处理这些任务的并发执行、超时控制以及错误处理,要求能够灵活配置每个任务的超时时间,并且在出现错误时能够方便地进行错误分类和统计。请用代码示例结合文字说明实现思路。
49.2万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
package main

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

// Task 定义任务类型
type Task struct {
    Name      string
    Timeout   time.Duration
    Exec      func(ctx context.Context) error
    ErrorType string
}

// TaskResult 定义任务结果类型
type TaskResult struct {
    TaskName  string
    Error     error
    ErrorType string
}

// RunTasks 并发运行任务
func RunTasks(tasks []Task) ([]TaskResult, error) {
    var results []TaskResult
    var overallErr error
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    resultChan := make(chan TaskResult, len(tasks))
    for _, task := range tasks {
        go func(t Task) {
            innerCtx, innerCancel := context.WithTimeout(ctx, t.Timeout)
            defer innerCancel()

            err := t.Exec(innerCtx)
            result := TaskResult{
                TaskName:  t.Name,
                Error:     err,
                ErrorType: t.ErrorType,
            }
            resultChan <- result
        }(task)
    }

    for i := 0; i < len(tasks); i++ {
        result := <-resultChan
        results = append(results, result)
        if result.Error != nil {
            overallErr = fmt.Errorf("%w; task %s failed with error: %v", overallErr, result.TaskName, result.Error)
        }
    }
    close(resultChan)

    return results, overallErr
}

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

// 示例文件读取任务
func fileRead(ctx context.Context) error {
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        // 模拟文件读取
        time.Sleep(3 * time.Second)
        return nil
    }
}

// 示例网络请求任务
func networkRequest(ctx context.Context) error {
    select {
    case <-ctx.Done():
        return ctx.Err()
    default:
        // 模拟网络请求
        time.Sleep(4 * time.Second)
        return nil
    }
}

实现思路

  1. 定义任务和结果类型
    • Task 结构体定义每个任务的属性,包括任务名称 Name、超时时间 Timeout、执行函数 Exec 以及错误类型 ErrorType
    • TaskResult 结构体定义任务执行后的结果,包括任务名称 TaskName、可能出现的错误 Error 以及错误类型 ErrorType
  2. 并发运行任务
    • RunTasks 函数负责并发运行所有任务。它使用 context.WithCancel 创建一个上下文,以便在任何任务出错时能够取消所有其他正在运行的任务。
    • 对于每个任务,使用 context.WithTimeout 创建具有特定超时时间的子上下文,并在 goroutine 中执行任务。
    • 每个任务执行完成后,将结果发送到 resultChan 通道。
  3. 收集结果和处理错误
    • 主程序从 resultChan 通道收集所有任务的结果,并统计出现的错误。如果有任何任务出错,RunTasks 函数返回的 overallErr 会包含错误信息。
  4. 示例任务函数
    • dbQueryfileReadnetworkRequest 是示例任务函数,它们模拟实际的数据库查询、文件读取和网络请求操作,并通过 select 语句来处理上下文的取消信号,以实现超时控制。

使用示例

func main() {
    tasks := []Task{
        {
            Name:      "DB Query",
            Timeout:   3 * time.Second,
            Exec:      dbQuery,
            ErrorType: "Database Error",
        },
        {
            Name:      "File Read",
            Timeout:   2 * time.Second,
            Exec:      fileRead,
            ErrorType: "File Error",
        },
        {
            Name:      "Network Request",
            Timeout:   5 * time.Second,
            Exec:      networkRequest,
            ErrorType: "Network Error",
        },
    }

    results, err := RunTasks(tasks)
    if err != nil {
        fmt.Println("Overall error:", err)
    }
    for _, result := range results {
        if result.Error != nil {
            fmt.Printf("Task %s failed with error type %s: %v\n", result.TaskName, result.ErrorType, result.Error)
        } else {
            fmt.Printf("Task %s succeeded\n", result.TaskName)
        }
    }
}

main 函数中,创建了三个不同类型的任务,并设置了各自的超时时间。通过调用 RunTasks 函数并发执行这些任务,并处理任务执行结果和错误。这样就实现了一个通用的框架来处理高并发任务的并发执行、超时控制以及错误处理。