MST

星途 面试题库

面试题:Go context 数据结构演变对并发编程的影响

结合 Go context 基本数据结构的演变,分析其对 Go 并发编程在资源管理、取消机制、跨协程数据传递等方面带来了哪些具体的影响,并且举例说明。
11.4万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

Go context基本数据结构演变

早期Go语言在并发编程中对于资源管理、取消机制等处理相对分散。随着context包引入,其基本数据结构Context接口及相关实现类型不断发展。Context接口定义了DeadlineDoneErrValue等方法,不同实现类型(如emptyCtxcancelCtxtimerCtxvalueCtx等)为不同场景提供支持。

对Go并发编程的影响

  1. 资源管理
    • 影响context通过Done通道和CancelFunc实现资源的及时释放。当父context取消时,所有基于该context创建的子context及其关联的协程都能收到取消信号,从而可以清理相关资源。例如在HTTP服务器处理请求时,当客户端断开连接,对应的context取消,处理该请求的协程可以停止不必要的数据库查询等操作,释放数据库连接等资源。
    • 示例
package main

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

func worker(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("worker: context cancelled, cleaning up resources")
            return
        default:
            fmt.Println("worker: working...")
            time.Sleep(100 * time.Millisecond)
        }
    }
}

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

    go worker(ctx)

    time.Sleep(500 * time.Millisecond)
}
  1. 取消机制
    • 影响context提供了统一且方便的取消机制。可以通过context.WithCancelcontext.WithTimeoutcontext.WithDeadline创建可取消的context。这种机制使得在复杂的并发场景中,能够精确控制协程的生命周期,避免出现资源泄露和无效计算。例如在一个爬虫程序中,当用户手动取消任务时,所有正在执行的抓取协程可以通过context的取消信号停止工作。
    • 示例
package main

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

func task(ctx context.Context) {
    select {
    case <-ctx.Done():
        fmt.Println("task: cancelled")
    case <-time.After(2 * time.Second):
        fmt.Println("task: completed")
    }
}

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

    go task(ctx)

    time.Sleep(3 * time.Second)
}
  1. 跨协程数据传递
    • 影响contextValue方法允许在不同协程之间传递请求作用域的数据,如用户认证信息、请求ID等。这种传递方式使得数据可以在整个请求处理的协程树中共享,而无需通过函数参数层层传递。例如在微服务架构中,一个请求在多个服务间传递时,通过context可以传递跟踪ID,方便进行分布式追踪。
    • 示例
package main

import (
    "context"
    "fmt"
)

func child(ctx context.Context) {
    value := ctx.Value("key")
    if value != nil {
        fmt.Println("child: received value:", value)
    }
}

func main() {
    ctx := context.WithValue(context.Background(), "key", "value")
    go child(ctx)

    time.Sleep(1 * time.Second)
}