MST

星途 面试题库

面试题:Go并发编程中上下文(Context)的继承与传递原理

在一个复杂的Go并发程序中,涉及多层函数调用和多个goroutine协作。请详细阐述上下文(Context)是如何在不同的goroutine和函数调用之间进行继承与传递的?并且说明如果在传递过程中出现上下文丢失或错误传递可能会导致什么问题,同时给出一个用于说明上下文正确传递的示例代码结构。
32.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

上下文(Context)的继承与传递

  1. 函数调用间传递:在Go中,通常将上下文作为函数参数,从调用者传递到被调用者。这样,即使在多层函数调用中,上下文也能被正确传递。例如,函数 func f(ctx context.Context, arg int) 接收上下文作为第一个参数,调用其他函数时也将该上下文传递下去,如 g(ctx, arg2)
  2. goroutine间传递:当启动一个新的goroutine时,同样需要将上下文传递进去。例如:
ctx := context.Background()
go func(ctx context.Context) {
    // 在新的goroutine中使用传递进来的上下文
}(ctx)

上下文对象可以通过闭包传递给匿名函数,使得新的goroutine能够继承上下文。如果在函数调用链中,某个函数需要启动新的goroutine,它会将接收到的上下文传递给这个新的goroutine,确保整个并发流程中上下文的一致性。

上下文丢失或错误传递导致的问题

  1. 资源泄漏:如果上下文丢失,例如在启动goroutine时没有传递上下文,那么当父goroutine结束时,新的goroutine可能无法感知,继续运行,导致资源(如文件描述符、数据库连接等)无法正确释放。
  2. 无法取消操作:上下文的一个重要功能是取消。如果上下文传递错误,子goroutine可能无法接收到取消信号,从而不能及时停止正在执行的任务,影响程序的正确性和性能。
  3. 超时控制失效:上下文还用于设置操作的超时。错误传递上下文可能导致超时控制无法在正确的范围内生效,使得长时间运行的任务无法按预期结束。

上下文正确传递的示例代码结构

package main

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

func worker(ctx context.Context, id int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Printf("Worker %d stopped\n", id)
            return
        default:
            fmt.Printf("Worker %d is working\n", id)
            time.Sleep(100 * time.Millisecond)
        }
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
    defer cancel()

    for i := 0; i < 3; i++ {
        go worker(ctx, i)
    }

    time.Sleep(1 * time.Second)
    fmt.Println("Main function is about to exit")
}

在这个示例中:

  1. main 函数创建了一个带超时的上下文 ctx,并通过 cancel 函数来取消上下文。
  2. 启动了3个 worker goroutine,每个 worker 都接收相同的上下文 ctx
  3. worker 函数通过 select 语句监听 ctx.Done() 通道,当接收到取消信号时,停止工作。这样确保了在主函数设置的超时时间到达后,所有的 worker goroutine 能够正确地停止,避免了资源泄漏等问题。