面试题答案
一键面试上下文(Context)的继承与传递
- 函数调用间传递:在Go中,通常将上下文作为函数参数,从调用者传递到被调用者。这样,即使在多层函数调用中,上下文也能被正确传递。例如,函数
func f(ctx context.Context, arg int)
接收上下文作为第一个参数,调用其他函数时也将该上下文传递下去,如g(ctx, arg2)
。 - goroutine间传递:当启动一个新的goroutine时,同样需要将上下文传递进去。例如:
ctx := context.Background()
go func(ctx context.Context) {
// 在新的goroutine中使用传递进来的上下文
}(ctx)
上下文对象可以通过闭包传递给匿名函数,使得新的goroutine能够继承上下文。如果在函数调用链中,某个函数需要启动新的goroutine,它会将接收到的上下文传递给这个新的goroutine,确保整个并发流程中上下文的一致性。
上下文丢失或错误传递导致的问题
- 资源泄漏:如果上下文丢失,例如在启动goroutine时没有传递上下文,那么当父goroutine结束时,新的goroutine可能无法感知,继续运行,导致资源(如文件描述符、数据库连接等)无法正确释放。
- 无法取消操作:上下文的一个重要功能是取消。如果上下文传递错误,子goroutine可能无法接收到取消信号,从而不能及时停止正在执行的任务,影响程序的正确性和性能。
- 超时控制失效:上下文还用于设置操作的超时。错误传递上下文可能导致超时控制无法在正确的范围内生效,使得长时间运行的任务无法按预期结束。
上下文正确传递的示例代码结构
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")
}
在这个示例中:
main
函数创建了一个带超时的上下文ctx
,并通过cancel
函数来取消上下文。- 启动了3个
worker
goroutine,每个worker
都接收相同的上下文ctx
。 worker
函数通过select
语句监听ctx.Done()
通道,当接收到取消信号时,停止工作。这样确保了在主函数设置的超时时间到达后,所有的worker
goroutine 能够正确地停止,避免了资源泄漏等问题。