模块 7 · 第 5 课(共 5)课程中的35/50~14 min
模块课程(5/5)
`context.Context`:取消和截止日期
context 包是 传播的事实上的标准
调用链上的取消、截止日期和范围限制值,
跨多个 goroutine。所有标准“网络”包
(net/http, database/sql, os/exec) 接受 context.Context 作为
他们的第一个参数。
界面
Go
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}Done():取消上下文时关闭的通道。Err():取消原因(context.Canceled或context.DeadlineExceeded)。Deadline():可选的截止日期。Value():查找范围限制值(谨慎使用)。
四个基本功能
Go
ctx := context.Background() // radice: usalo in main, init, test
ctx := context.TODO() // "non so ancora": stub esplicito
ctx, cancel := context.WithCancel(parent) // cancel manuale
ctx, cancel := context.WithTimeout(parent, d) // scadenza relativa
ctx, cancel := context.WithDeadline(parent, t) // scadenza assoluta
ctx := context.WithValue(parent, key, value) // valore scope-bound所有派生形式都返回 cancel 函数:始终调用它
(理想情况下使用 defer)释放内部资源,即使
上下文已经过期。
超时模式
Go
func fetchUser(id string) (*User, error) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
return queryUser(ctx, id) // queryUser deve rispettare ctx
}如果 queryUser 花费超过 2 秒,则 ctx.Done() 将关闭并
该函数必须中止操作。
尊重上下文:选择 Done()
Go
func slowWork(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return ctx.Err() // canceled o deadline exceeded
case <-time.After(100 * time.Millisecond):
// un tick di lavoro
}
}
}规则:任何地方你的函数可能会阻塞很长时间, 必须处理 ctx.Done()。否则,上下文将“通电” 什么也没有。
第一参数约定
Go
func Fetch(ctx context.Context, url string) ([]byte, error) { ... }
// ^^^^^^^^^^^^^^^^^^^
// SEMPRE primo, mai dentro a una struct, mai opzionalectx始终作为第一个参数,名为ctx。- 永远不要将
Context作为结构体的字段(非常罕见的例外)。 - 从不传递
nil:使用context.TODO()作为存根。
context.WithValue:谨慎
Go
type userKey struct{}
ctx := context.WithValue(ctx, userKey{}, currentUser)
// più sotto:
if u, ok := ctx.Value(userKey{}).(*User); ok { ... }指南:
- 仅适用于 请求范围 数据(请求 ID、跟踪 ID、用户身份)。
- 绝不用于函数参数:正常传递它们。
- 自定义非导出类型的密钥以避免冲突。
沿链传播
Go
func handleHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // context legato al ciclo di vita della richiesta
user, err := fetchUser(ctx, r.URL.Query().Get("id"))
if err != nil { ... }
// ...
}net/http 当客户端自动取消 r.Context()
断开连接或处理程序返回时。通过向所有人传播
下游调用,整个操作树被取消
级联:断开连接后不会浪费工作。
尝试一下
锻炼#go.m7.l5.e1
尝试:0加载中...
创建一个具有从 context.Background() 派生的 1 秒超时的上下文,并记住使用 defer 调用 cancel。
正在加载编辑器...
显示提示
`context.WithTimeout(parent, d)` 返回 `(ctx, cancel)`;总是打电话取消。
3 次尝试后可用的解决方案
锻炼#go.m7.l5.e2
尝试:0加载中...
在选择中使用 <-ctx.Done() 等待上下文被取消。
正在加载编辑器...
显示提示
`ctx.Done()` 是一个在上下文取消时关闭的通道。
3 次尝试后可用的解决方案
Quiz#go.m7.l5.e3
准备好按照惯例,ctx 参数在函数签名中位于哪里?
Go
func Fetch(???, url string) ([]byte, error)回顾
context.Context传播取消、截止日期和范围限制值。Background()/TODO()作为根;WithCancel/WithTimeout/WithDeadline来派生。- 始终使用
defer cancel()来释放资源。 - “长”函数必须在
ctx.Done()上select或将 ctx 传递到标准包。 - 约定:
ctx作为第一个参数,切勿在结构内。 WithValue仅适用于请求范围的数据(请求 ID、跟踪 ID)。- 在
net/http中,r.Context()在客户端断开连接时被取消。