Go使用context控制协程取消的实战案例

Written by

in

文章目录
  • context 是 Go 1.7 起加入标准库的一个重要包,用于跨 API 边界传递取消信号、超时时间、截止时间等信息。 主要接口定义如下: type Context interface { Deadline() (deadline time.Time, ok bool) Done() <-chan struct{} Err() error Value(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() 控制协程的停止。
  • 除了手动调用 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 控制它们: 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) return default: 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 程序更稳定、更可控! 以上就是Go使用context控制协程取消的实战案例的详细内容,更多关于Go context控制协程取消的资料请关注风君子博客其它相关文章! 您可能感兴趣的文章: 使用Go语言中的Context取消协程执行的操作代码 Go使用chan或context退出协程示例详解 Go语言中Context的实现示例 golang中context.WithValue的使用规范问题小结 go语言中context的使用说明
  • 目录
    • 一、什么是 context
    • 二、常见创建方式
    • 三、实战案例:使用 context 控制任务协程
      • 场景描述
      • 示例代码
      • 输出结果
    • 四、使用 context.WithTimeout 实现超时控制
      • 五、多个协程共享一个 context
        • 六、最佳实践建议
          • 七、结语

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

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

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

            主要接口定义如下:

            type Context interface {
                Deadline() (deadline time.Time, ok bool)
                Done() <-chan struct{}
                Err() error
                Value(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() 控制协程的停止。

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

            package main
            
            import (
                "context"
                "fmt"
                "time"
            )
            
            func worker(ctx context.Context) {
                for {
                    select {
                    case <-ctx.Done():
                        fmt.Println("worker 任务被取消:", ctx.Err())
                        return
                    default:
                        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,协程立即响应退出了。

            除了手动调用 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 控制它们:

            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)
                                return
                            default:
                                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 程序更稳定、更可控!

            以上就是Go使用context控制协程取消的实战案例的详细内容,更多关于Go context控制协程取消的资料请关注风君子博客其它相关文章!

            您可能感兴趣的文章:

            • 使用Go语言中的Context取消协程执行的操作代码
            • Go使用chan或context退出协程示例详解
            • Go语言中Context的实现示例
            • golang中context.WithValue的使用规范问题小结
            • go语言中context的使用说明

            站内搜索