当前位置: 首页 > ops >正文

Go语言实战案例:使用context控制协程取消

在并发编程中,合理地控制协程(goroutine)的生命周期是保证程序稳定性和资源可控使用的关键。Go语言标准库中的 context 包正是为了解决这一问题而生。它为我们提供了取消信号、超时控制、请求作用域的值传递等功能。

本文将通过一个实际案例,演示如何使用 context 控制协程的取消,避免资源泄露,实现优雅退出。


一、什么是 context

context 是 Go 1.7 起加入标准库的一个重要包,用于跨 API 边界传递取消信号、超时时间、截止时间等信息。

主要接口定义如下:

type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key any) any
}

其中最关键的是:

  • • Done():返回一个 channel,当 context 被取消或超时关闭时,该 channel 会被关闭;
  • • Err():返回取消的原因,例如 context.Canceled 或 context.DeadlineExceeded

二、常见创建方式

Go 提供了以下常用方式创建 context:

ctx := context.Background() // 最顶层、永不取消的 context
ctx, cancel := context.WithCancel(parent) // 手动调用 cancel() 取消
ctx, cancel := context.WithTimeout(parent, 3*time.Second) // 指定超时时间
ctx, cancel := context.WithDeadline(parent, time.Now().Add(3*time.Second)) // 到期时间点

这些 context 都可以传递到协程中,通过 ctx.Done() 控制协程的停止。


三、实战案例:使用 context 控制任务协程

场景描述

假设我们要运行一个任务,该任务每秒输出一次“正在处理”,但当主程序在某个时机需要终止它(比如点击“停止按钮”或超时),我们要优雅地通知协程退出。

示例代码

package mainimport ("context""fmt""time"
)func worker(ctx context.Context) {for {select {case <-ctx.Done():fmt.Println("worker 任务被取消:", ctx.Err())returndefault:fmt.Println("worker 正在处理任务...")time.Sleep(1 * time.Second)}}
}func main() {ctx, cancel := context.WithCancel(context.Background())go worker(ctx)// 主线程运行5秒后取消任务time.Sleep(5 * time.Second)cancel()// 等待协程打印结束语time.Sleep(1 * time.Second)fmt.Println("主程序退出")
}

输出结果

worker 正在处理任务...
worker 正在处理任务...
worker 正在处理任务...
worker 正在处理任务...
worker 正在处理任务...
worker 任务被取消: context canceled
主程序退出

可以看到,主程序通过 cancel() 取消了 context,协程立即响应退出了。


四、使用 context.WithTimeout 实现超时控制

除了手动调用 cancel,我们还可以通过设定超时时间自动取消任务。

func main() {ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)defer cancel()go worker(ctx)time.Sleep(5 * time.Second) // 主线程等待更久,看任务是否能自动终止fmt.Println("主程序退出")
}

运行结果类似:

worker 正在处理任务...
worker 正在处理任务...
worker 正在处理任务...
worker 任务被取消: context deadline exceeded
主程序退出

这表示:即使主程序没有主动调用 cancel(),协程也在 3 秒后收到 context 超时通知并正常退出。


五、多个协程共享一个 context

我们可以启动多个协程,并使用同一个 context 控制它们:

func main() {ctx, cancel := context.WithCancel(context.Background())for i := 1; i <= 3; i++ {go func(id int) {for {select {case <-ctx.Done():fmt.Printf("协程 %d 接收到取消信号\n", id)returndefault:fmt.Printf("协程 %d 正在工作\n", id)time.Sleep(1 * time.Second)}}}(i)}time.Sleep(4 * time.Second)cancel()time.Sleep(1 * time.Second)fmt.Println("主程序退出")
}

输出:

协程 1 正在工作
协程 2 正在工作
协程 3 正在工作
...(多次输出)
协程 1 接收到取消信号
协程 2 接收到取消信号
协程 3 接收到取消信号
主程序退出

这样我们实现了“一键终止所有协程”。


六、最佳实践建议

  • • 永远使用 context.WithCancel / WithTimeout 返回的 cancel() 函数,不要忘记 defer cancel()
  • • 在需要可控中止的任务(如网络请求、数据库操作、循环处理)中传入 context;
  • • 对 ctx.Done() 的监听要放在协程内部适当位置,防止资源泄露;
  • • 在请求链中传递 context,以实现链路级别的取消与超时控制。

七、结语

context 是 Go 并发控制中不可或缺的利器。它不仅解决了协程取消的痛点,还能为任务设置统一的生命周期控制逻辑,是构建高可靠网络服务、后台任务系统、爬虫等并发程序的基础设施。

如果你还没有在项目中大量使用它,不妨从今天开始重构代码,使用 context 管理协程生命周期,让你的 Go 程序更稳定、更可控!

http://www.xdnf.cn/news/17077.html

相关文章:

  • 算法训练之哈希表
  • Java后端高频面试题
  • React在使用create-react-app创建项目慢的解决办法
  • python的高校考研交流系统
  • 基于ARM+FPGA多通道超声信号采集与传输系统设计
  • 广州客户 戴尔R720服务器 liunx系统 RAID5无损升级扩容
  • 注意点:Git 从安装到分支协作、冲突解决的完整步骤 ---待修改,没看这个步骤,需要重新整理步骤
  • JavaWeb(苍穹外卖)--学习笔记17(Websocket)
  • 国产三防平板电脑是什么?三防平板推荐
  • 前端包管理器深度对比
  • VUE2 学习笔记18 路由守卫
  • Mysql使用Canal服务同步数据->ElasticSearch
  • 数据挖掘,到底是在挖掘什么?
  • Golang 基本数据类型
  • 智慧工业复杂目标检测精度跃升:陌讯多模态融合算法实战解析
  • mac前端环境安装
  • 机器学习之KNN、贝叶斯与决策树算法
  • 自动驾驶控制算法——MPC控制算法
  • 浮雕软件Artcam安装包百度云网盘下载与安装指南
  • Redis(六):分布式锁
  • 【机器学习深度学习】 知识蒸馏
  • 分布式网关技术 + BGP EVPN,解锁真正的无缝漫游
  • Java面试宝典:深入解析JVM运行时数据区
  • 计算机网络:(十三)传输层(中)用户数据报协议 UDP 与 传输控制协议 TCP 概述
  • python+MySQL组合实现生成销售财务报告
  • AI的第一次亲密接触——你的手机相册如何认出你的猫?
  • QUdpSocket发送组播和接受组播数据
  • Modstart 请求出现 Access to XMLHttpRequest at ‘xx‘
  • FPGA学习笔记——简易的DDS信号发生器
  • Cisco 3750X交换机更新到IOS 15.2后无法启动 提示:Boot process failed...