面试题答案
一键面试设计思路
- 传递上下文:在整个调用链中,从顶层函数向底层函数传递
context.Context
。当一个RPC调用发起时,将上层传入的context
传递给被调用的服务,被调用服务开启的新协程也使用该context
。这样当顶层的context
被取消时,整个调用链上基于该context
的所有协程都能收到取消信号。 - 控制任务生命周期:
context
可以控制协程的生命周期。例如,当一个请求超时时,通过取消context
来通知所有相关协程停止工作,避免协程无限制地运行,浪费资源。 - 资源释放:在协程退出时,确保所有相关资源(如数据库连接、文件句柄等)都能正确释放。通过监听
context
的取消信号,在收到信号后进行资源清理工作。
关键实现要点
- 创建合适的
context
:根据需求选择context.Background()
、context.TODO()
、context.WithCancel()
、context.WithTimeout()
或context.WithDeadline()
。通常在入口处使用context.Background()
或context.TODO()
,然后根据具体需求使用WithCancel
、WithTimeout
等衍生出更具体控制的context
。 - 传递
context
:在函数调用和协程启动时,始终将context
作为参数传递。无论是RPC调用还是本地函数调用开启新协程,都要保证context
能正确传递。 - 监听取消信号:在协程内部,使用
select
语句监听context.Done()
通道。当该通道接收到数据时,意味着context
被取消,协程应尽快结束工作。
示例代码及解释
package main
import (
"context"
"fmt"
"net/http"
"time"
)
// 模拟一个RPC服务
func rpcService(ctx context.Context) error {
select {
case <-time.After(2 * time.Second):
// 模拟正常工作完成
return nil
case <-ctx.Done():
// context被取消,返回错误
return ctx.Err()
}
}
// 处理HTTP请求的函数
func handler(w http.ResponseWriter, r *http.Request) {
// 创建一个带超时的context
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// 开启一个新协程执行RPC服务
go func() {
err := rpcService(ctx)
if err != nil {
fmt.Println("RPC service error:", err)
}
}()
// 模拟其他工作
time.Sleep(1 * time.Second)
fmt.Fprintf(w, "Request processed")
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("Server listening on :8080")
http.ListenAndServe(":8080", nil)
}
在上述代码中:
handler
函数处理HTTP请求,创建了一个带1秒超时的context
。- 使用
go
关键字开启一个新协程执行rpcService
函数,并将context
传递进去。 rpcService
函数内部通过select
语句监听context.Done()
通道。如果在2秒内context
没有被取消,服务正常完成;如果context
被取消(这里是1秒超时),则返回错误。defer cancel()
确保在handler
函数结束时,取消context
,防止资源泄露,所有基于该context
的协程都会收到取消信号并停止工作。