MST

星途 面试题库

面试题:Go中context在复杂业务场景下管理子协程退出的应用

假设你正在开发一个微服务,该服务需要调用多个下游服务,每个下游服务调用在一个独立的子协程中执行。当收到外部取消信号时,如何使用context确保所有子协程能有序、及时地退出,同时避免资源泄漏?请详细描述设计思路,并给出关键代码片段。
44.9万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 创建上下文:在微服务的入口创建一个 context.Context,通常使用 context.Background() 作为根上下文,然后基于此创建可取消的上下文,如 ctx, cancel := context.WithCancel(context.Background())。这个 cancel 函数将用于在接收到取消信号时取消上下文。
  2. 传递上下文:将创建的上下文传递给每个调用下游服务的子协程。每个子协程在执行下游服务调用时,需要定期检查上下文的取消信号,例如在调用下游服务的函数中使用 ctx.Done() 通道来判断是否已取消。
  3. 处理取消信号:当收到外部取消信号(例如通过操作系统信号、HTTP 中断等),调用 cancel() 函数取消上下文。所有子协程在检测到上下文取消后,应尽快结束当前操作,释放资源并退出。

关键代码片段(以 Go 语言为例)

package main

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

// 模拟下游服务调用
func downstreamService(ctx context.Context, name string, wg *sync.WaitGroup) {
    defer wg.Done()
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Service %s received cancel signal, exiting...\n", name)
            return
        default:
            fmt.Printf("Service %s is running...\n", name)
            time.Sleep(1 * time.Second)
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    var wg sync.WaitGroup

    // 启动多个子协程调用下游服务
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go downstreamService(ctx, fmt.Sprintf("Service-%d", i), &wg)
    }

    // 模拟接收到外部取消信号
    go func() {
        time.Sleep(3 * time.Second)
        fmt.Println("Received cancel signal")
        cancel()
    }()

    wg.Wait()
    fmt.Println("All services have exited")
}

在上述代码中:

  1. context.WithCancel 创建了一个可取消的上下文 ctx 和取消函数 cancel
  2. downstreamService 函数模拟下游服务调用,通过 select 语句监听 ctx.Done() 通道,一旦接收到取消信号,就退出循环并结束函数。
  3. main 函数中,启动了多个子协程调用 downstreamService,并在 3 秒后模拟接收到外部取消信号,调用 cancel 函数取消上下文,最终等待所有子协程结束。