Go语言Context应用全攻略:异步编程利器( 二 )


4. Context 的链式操作在实际应用中,可能需要将多个 Context 串联起来使用 。
Go 语言的 Context 提供了 WithCancel、WithDeadline、WithTimeout 等方法 。
可以用这些方法实现多个 Context 的协同工作 。
package mainimport ("context""fmt""time")func main() {// 创建一个根ContextrootContext := context.Background()// 创建一个超时时间为2秒的ContexttimeoutCtx, _ := context.WithTimeout(rootContext, 2*time.Second)// 创建一个手动取消的ContextcancelCtx, cancel := context.WithCancel(rootContext)defer cancel()// 在新的goroutine中执行任务go func(ctx context.Context) {select {case <-time.After(3 * time.Second):fmt.Println("任务完成")case <-ctx.Done():fmt.Println("任务取消或超时")}}(timeoutCtx)// 在另一个goroutine中执行任务go func(ctx context.Context) {select {case <-time.After(1 * time.Second):fmt.Println("另一个任务完成")case <-ctx.Done():fmt.Println("另一个任务取消")}}(cancelCtx)// 等待一段时间,模拟程序运行time.Sleep(5 * time.Second)}在示例中,创建了一个带有 2 秒超时时间的 Context 和一个可以手动取消的 Context,然后分别传递给两个不同的任务 。
在主函数中,等待了 5 秒,超时时间为 2 秒 , 因此第一个任务会因超时而取消,第二个任务则会在 1 秒后完成 。
5. Context 在并发中的应用5.1 使用 Context 控制多个协程package mainimport ("context""fmt""sync""time")func main() {// 创建一个根ContextrootContext := context.Background()// 创建一个可以手动取消的Contextctx, cancel := context.WithCancel(rootContext)defer cancel()// 使用WaitGroup等待所有任务完成var wg sync.WaitGroup// 启动多个协程执行任务for i := 0; i < 5; i++ {wg.Add(1)go func(id int) {defer wg.Done()select {case <-time.After(time.Duration(id) * time.Second):fmt.Println("任务", id, "完成")case <-ctx.Done():fmt.Println("任务", id, "取消")}}(i)}// 等待一段时间,然后手动取消任务time.Sleep(2 * time.Second)cancel()// 等待所有任务完成wg.Wait()}在上面例子中,创建了一个可以手动取消的 Context,并使用 sync.WaitGroup 等待所有任务完成 。
在 for 循环中,启动了 5 个协程 , 每个协程会等待一段时间后输出任务完成信息 。
在主函数中,程序等待了 2 秒后,手动调用 cancel 函数取消了任务 , 协程会接收到取消信号并退出 。
5.2 避免 Context 滥用在使用 Context 时,要避免将 Context 放在结构体中 。
因为 Context 应该作为函数参数传递,而不应该被放在结构体中进行传递 。
Context 应该限定在程序的最小作用域,不要传递到不需要它的函数中 。
6. Context 的应用场景6.1 HTTP 请求中的 Context 使用package mainimport ("fmt"".NET/http""time")func handler(w http.ResponseWriter, r *http.Request) {ctx := r.Context()select {case <-time.After(2 * time.Second):fmt.Fprintln(w, "Hello, World!")case <-ctx.Done():err := ctx.Err()fmt.Println("Server:", err)http.Error(w, err.Error(), http.StatusInternalServerError)}}func main() {http.HandleFunc("/", handler)http.ListenAndServe(":8080", nil)}在上面示例中,创建了一个 HTTP 请求处理函数 handler 。
在处理函数中,用 r.Context() 获取到请求的 Context,并在其中执行一个耗时的任务 。
如果请求超时 , ctx.Done() 会接收到取消信号,可以在其中处理请求超时的逻辑 。
6.2 数据库操作中的 Context 使用package mainimport ("context""database/sql""fmt""time"_ "Github.com/go-sql-driver/MySQL")func main() {// 连接数据库db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/database")if err != nil {fmt.Println("数据库连接失败:", err)return}defer db.Close()// 创建一个Context,设置超时时间为5秒ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()// 在Context的超时时间内执行数据库查询rows, err := db.QueryContext(ctx, "SELECT * FROM users")if err != nil {fmt.Println("数据库查询失败:", err)return}defer rows.Close()// 处理查询结果for rows.Next() {// 处理每一行数据}}在上面例子中 , 使用 database/sql 包进行数据库查询 。创建了一个带有 5 秒超时时间的 Context,并在其中执行数据库查询 。
如果查询时间超过 5 秒,Context 会接收到取消信号 , 可以在其中执行处理查询超时的逻辑 。
6.3 其他业务场景中的 Context 使用在其他业务场景中,可使用 Context 实现更多复杂的任务协同 。


推荐阅读