使用context实现超时取消需创建带时限的子context,传递至各调用层,操作完成后调用cancel释放资源,通过监听ctx.Done()及时响应取消信号,确保全链路超时控制。

在Go语言开发中,context 是控制协程生命周期的核心工具,尤其适用于处理超时和取消场景。通过 context,我们可以在请求链路中传递取消信号,及时释放资源,避免 goroutine 泄漏和响应延迟。
使用 WithTimeout 实现超时控制
最常见的超时取消模式是使用 context.WithTimeout。它会返回一个带有自动取消功能的 context,在指定时间后触发取消信号。
- 调用
context.WithTimeout(parent, timeout)创建子 context - 函数执行完成后必须调用 cancel() 释放资源
- 被监控的操作需要监听 ctx.Done() 以响应取消
示例:限制一个网络请求最多执行500毫秒
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel()req, _ := http.NewRequest("GET", "https://www.php.cn/link/b05edd78c294dcf6d960190bf5bde635", nil) req = req.WithContext(ctx) // 将 ctx 绑定到请求
client := &http.Client{} resp, err := client.Do(req) if err != nil { if ctx.Err() == context.DeadlineExceeded { log.Println("请求超时") } else { log.Printf("请求失败: %v", err) } return } defer resp.Body.Close() // 处理响应
结合 select 监听取消信号
对于自定义耗时操作(如模拟计算或轮询),可以手动监听 context 的 Done 通道,配合 select 实现非阻塞等待。
立即学习“go语言免费学习笔记(深入)”;
- 在 for 循环中使用 select 判断是否收到取消指令
- 一旦 ctx.Done() 可读,立即退出当前逻辑
- 合理设置重试间隔,避免忙等
示例:带超时的轮询任务
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel()ticker := time.NewTicker(500 * time.Millisecond) defer ticker.Stop()
for { select { case <-ctx.Done(): log.Println("任务取消:", ctx.Err()) return case <-ticker.C: fmt.Println("正在轮询...") // 模拟工作 if someCondition() { fmt.Println("条件满足,任务完成") return } } }
传播 context 实现链式取消
实际项目中,一次请求可能涉及多个服务调用(数据库、RPC、缓存等)。将同一个 context 传入各个子函数,可实现统一的超时控制。
- 入口层创建带超时的 context
- 逐层传递 ctx 至下游函数
- 任一环节超时,整个调用链都会收到取消信号
示例:API 请求处理链
func handleRequest(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
result, err := fetchUserData(ctx, "user123")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
http.Error(w, "服务繁忙,请稍后再试", 504)
return
}
http.Error(w, "内部错误", 500)
return
}
json.NewEncoder(w).Encode(result)}
func fetchUserData(ctx context.Context, uid string) (User, error) {
queryCtx, cancel := context.WithTimeout(ctx, 800time.Millisecond)
defer cancel()
row := db.QueryRowContext(queryCtx, "SELECT name FROM users WHERE id = ?", uid)
// 如果上层已超时,这里会立即失败
var name string
if err := row.Scan(&name); err != nil {
return nil, err
}
return &User{Name: name}, nil}
基本上就这些。掌握 context 超时取消的关键在于:始终传递 ctx、及时调用 cancel、检查 ctx.Err()。只要每层都正确响应取消信号,就能构建出高效可控的并发程序。










