首页 > 其他分享 >go语言Context应用全讲解

go语言Context应用全讲解

时间:2023-11-05 17:35:26浏览次数:31  
标签:Context fmt time ctx context 讲解 go 超时

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

原创 Go先锋 Go先锋 2023-11-05 11:42 发表于广东 收录于合集#Go语言包32个

Go 先锋

读完需要

17分钟

速读仅需 6 分钟

   

概述

在 Go 语言中,Context(上下文)是一个非常重要的概念,特别是在处理请求时。

允许在请求的整个生命周期内传递数据、控制请求的取消、处理超时等。

本文将介绍 Go 语言中 Context 的使用,帮助更好地理解与处理请求的传递与控制。

主要内容包括

  • Context 基础

  • Context 创建与传递

  • Context 的超时与取消

  • Context 的链式操作

  • Context 在并发中的应用

  • Context 的应用场景

  • 最佳实践与注意事项

 

   

1. Context 基础

在 Go 语言中,context.Context 接口定义了一个请求的上下文。

它包含了请求的截止时间、取消信号和请求的数据。

使用 Context 可以在请求之间有效地传递数据,同时也可以控制请求的生命周期。

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

Context 接口包含了四个方法:Deadline() 返回 Context 的截止时间

Done() 返回一个通道,它会在 Context 被取消或超时时关闭

Err() 返回 Context 的错误信息,Value(key) 返回 Context 中与 key 关联的值。

 

   

2. Context 创建与传递

2.1 创建和传递 Context

package main
import ( "context" "fmt" "time")
func main() { // 创建一个根Context rootContext := context.Background()
// 创建一个带有超时时间的Context,这里设置超时时间为2秒 ctx, cancel := context.WithTimeout(rootContext, 2*time.Second) defer cancel()
// 在新的goroutine中执行任务 go func(ctx context.Context) { select { case <-time.After(3 * time.Second): fmt.Println("任务完成") case <-ctx.Done(): fmt.Println("任务取消或超时") } }(ctx) // 等待一段时间,模拟程序运行 time.Sleep(5 * time.Second)}

在这个例子中,创建了一个带有 2 秒超时时间的 Context,并在一个新的 goroutine 中执行一个任务。

在主 goroutine 中,等待了 5 秒,因此任务在超时之前完成,所以会输出"任务完成"。

2.2 使用 WithValue 传递数据

package main
import ( "context" "fmt")
type key string
func main() { // 创建一个根Context rootContext := context.Background()
// 使用WithValue传递数据 ctx := context.WithValue(rootContext, key("userID"), 123)
// 在子函数中获取传递的数据 getUserID(ctx)}
func getUserID(ctx context.Context) { // 从Context中获取数据 if userID, ok := ctx.Value(key("userID")).(int); ok { fmt.Println("UserID:", userID) } else { fmt.Println("UserID不存在") }}

在这个示例中,使用 WithValue 方法在 Context 中传递了一个 userID 的值,并在 getUserID 函数中成功获取并打印了这个值。

 

   

3. Context 的超时与取消

3.1 设置请求超时时间

package main
import ( "context" "fmt" "time")
func main() { // 创建一个根Context rootContext := context.Background()
// 创建一个超时时间为2秒的Context timeoutCtx, _ := context.WithTimeout(rootContext, 2*time.Second)
// 创建一个手动取消的Context cancelCtx, 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)}

在上面例子中,用 WithTimeout 方法创建了一个带有 2 秒超时时间的 Context。

在任务的 goroutine 中,用 select 语句监听了超时和 Context 的取消两个事件,以便及时响应。

3.2 处理请求取消

package main
import ( "context" "fmt" "time")
func main() { // 创建一个根Context rootContext := context.Background()
// 创建一个可以手动取消的Context ctx, 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("任务取消") } }(ctx)
// 等待一段时间,手动取消任务 time.Sleep(2 * time.Second) cancel()
// 等待一段时间,模拟程序运行 time.Sleep(1 * time.Second)}

在上面例子中,使用 WithCancel 方法创建了一个可以手动取消的 Context。

在主函数中,等待了 2 秒后,手动调用 cancel 函数取消了任务。

这时,在任务的 goroutine 中,ctx.Done() 会接收到取消信号,从而退出任务。

 

   

4. Context 的链式操作

在实际应用中,可能需要将多个 Context 串联起来使用。

Go 语言的 Context 提供了 WithCancel、WithDeadline、WithTimeout 等方法。

可以用这些方法实现多个 Context 的协同工作。

package main
import ( "context" "fmt" "time")
func main() { // 创建一个根Context rootContext := context.Background()
// 创建一个超时时间为2秒的Context timeoutCtx, _ := context.WithTimeout(rootContext, 2*time.Second)
// 创建一个手动取消的Context cancelCtx, 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 main
import ( "context" "fmt" "sync" "time")
func main() { // 创建一个根Context rootContext := context.Background()
// 创建一个可以手动取消的Context ctx, 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 main
import ( "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 main
import ( "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 实现更多复杂的任务协同。

例如,使用 Context 在多个微服务之间进行数据传递和超时控制。

以下是一个示例,演示了如何在微服务架构中使用 Context 进行跨服务的数据传递

package main
import ( "context" "fmt" "time")
type Request struct { ID int}
type Response struct { Message string}
func microservice(ctx context.Context, reqCh chan Request, resCh chan Response) { for { select { case <-ctx.Done(): fmt.Println("Microservice shutting down...") return case req := <-reqCh: // 模拟处理请求的耗时操作 time.Sleep(2 * time.Second) response := Response{Message: fmt.Sprintf("Processed request with ID %d", req.ID)} resCh <- response } }}
func main() { // 创建根Context rootContext := context.Background()
// 创建用于请求和响应的通道 reqCh := make(chan Request) resCh := make(chan Response)
// 启动微服务 go microservice(rootContext, reqCh, resCh)
// 创建带有5秒超时时间的Context ctx, cancel := context.WithTimeout(rootContext, 5*time.Second) defer cancel()
// 发送请求到微服务 for i := 1; i <= 3; i++ { req := Request{ID: i} reqCh <- req
select { case <-ctx.Done(): fmt.Println("Request timed out!") return case res := <-resCh: fmt.Println(res.Message) } }}

在上面示例中,创建了一个简单的微服务模拟,它接收来自 reqCh 通道的请求,并将处理结果发送到 resCh 通道。

在主函数中,用带有 5 秒超时时间的 Context 来确保请求不会无限期等待,同时也能够处理超时的情况。

 

   

7. 最佳实践与注意事项

7.1 避免在函数库中使用 Context

通常情况下,应该在函数的参数列表中显式传递 Context,而不是将 Context 放在结构体中。

这样做可以使函数的行为更加明确,避免隐藏传递的 Context,提高代码的可读性和可维护性。

7.2 避免在结构体中嵌入 Context

尽管可以将 Context 作为结构体的成员嵌入,但这样的做法通常是不推荐的。

因为 Context 应该是在函数调用的时候传递,而不是嵌入在结构体中。

如果结构体的方法需要使用 Context,应该将 Context 作为参数传递给这些方法。

7.3 注意 Context 的传递路径

在实际应用中,要仔细考虑 Context 的传递路径。

若是在多个函数之间传递 Context,确保 Context 的传递路径清晰明了,避免出现歧义和混乱。

Context 的传递路径应该尽量短,不要跨越过多的函数调用。

 

   

总结

在 Go 语言中,Context 是一个强大的工具,用于处理请求的传递、控制和超时等。

通过合理地使用 Context,可以编写出更加稳定、高效的异步程序,提高系统的健壮性和可维护性。

 

Go先锋

赞赏二维码稀罕作者

收录于合集 #Go语言包  32个 上一篇Go语言插件开发:Pingo库实践   阅读 171 Go先锋 ​ 喜欢此内容的人还喜欢   Go语言数学运算大揭秘:高精度计算实战     我看过的号 Go先锋 不看的原因   Go并发编程之四     程序员青菜学厨记 不看的原因   Golang与广度优先搜索(BFS)     我看过的号 明日代码 不看的原因   写留言            

人划线

 

标签:Context,fmt,time,ctx,context,讲解,go,超时
From: https://www.cnblogs.com/cheyunhua/p/17810778.html

相关文章

  • 复习 Golang Chapter 2 原始类型和声明
    内建类型的使用变量与常量的使用以及惯例写一些代码,看一看如何"最好"的运用他们,关于什么是“最好”,这里有一个最主要的原则:让你的意图能够透过代码清晰的表示出来内建类型Built-inTypes惯用法是跨语言使用者的障碍,学一门新的编程语言,主要是向这个方向靠拢(可通过开源代码和......
  • 使用Gorm进行高级查询
    使用Gorm进行高级查询原创 Slagga 技术的游戏 2023-11-0422:42 发表于广东收录于合集#Golang83个深入探讨GORM的高级查询功能,轻松实现Go中的数据检索高效的数据检索是每个应用程序性能的核心。GORM,强大的Go对象关系映射库,不仅扩展到基本的CRUD操作,还提供了高级的......
  • [ABC327G] Many Good Tuple Problems 题解
    题意对于一对长度均为\(M\)且元素值在\(\left[1,N\right]\)之间的序列\((S,T)\),定义其为好的当且仅当:存在一个长度为\(N\)的\(01\)序列\(X\),使得其满足如下条件:对于任意\(i\in\left[1,M\right]\),有\(X_{S_i}\neqX_{T_i}\)。给定\(N,M\),求在所有可......
  • Django 表单处理:从前端到后台的全流程指南
    Django作为一个高级PythonWeb框架,它的表单处理能力强大,可以有效地处理用户输入,进行数据验证以及错误处理。本文将详细介绍如何在Django中创建、处理和使用表单。1.Django表单系统的核心Django的表单系统处理表单的生命周期,涉及以下核心部分:表单类:定义表单的结构和行为。验......
  • 2023-11-04:用go语言,如果n = 1,打印 1*** 如果n = 2,打印 1*** 3*** 2*** 如果n = 3,打印
    2023-11-04:用go语言,如果n=1,打印1***如果n=2,打印1***3***2***如果n=3,打印1***3***2***4***5***6***如果n=4,打印1***......
  • 区块链平台终极对决:Solana Vs. Polygon Vs. Ethereum
    区块链技术是目前世界上最受关注的技术之一。它几乎进入了每一个领域,以其中心化的系统来改善现有技术。随着区块链的进入,也为这些细分领域开发了许多不同种类的应用。它还催生了诸如NFT、去中心化金融(Defi)、加密货币等很多东西。原文:https://www.blockchain-council.org/b......
  • Checkerboard Context Model for Efficient Learned Image Compression
    目录AbstractIntroductionPreliminary初步介绍VariationalImageCompressionwithHyperprior(超先验变分图像压缩)AutoregressiveContext(自回归上下文模型)ParallelContextModeling并行上下文模型Random-MaskModel:TestArbitraryMasks(随机掩码模型)HowDistanceInfl......
  • Go 方法介绍,理解“方法”的本质
    Go方法介绍,理解“方法”的本质目录Go方法介绍,理解“方法”的本质一、认识Go方法1.1基本介绍1.2声明1.2.1引入1.2.2一般声明形式1.2.3receiver参数作用域1.2.4receiver参数的基类型约束1.2.5方法声明的位置约束1.2.6如何使用方法二、方法的本质三、巧解难题一、认......
  • 复习 Golang Chapter 1 开发环境与配置
    学习安装以及配置常见的Go环境变量用于开发环境学习Go的一些基本命令以及工具(Makefile)如何安装与组织你的目录go编译器的安装方法直接上官方网站按自己的操作系统来youarefreetoorganizeyourprojectsasyouseefit.环境变量你安装的third-party工具所在......
  • 无涯教程-MongoDB - 上限集合
    上限集合是固定大小的循环集合,遵循插入顺序以支持高性能的创建,读取和删除操作。循环表示这意味着分配给集合的固定大小用尽时,它将开始删除集合中最旧的文档,而无需提供任何显式命令。创建上限集合要创建一个有上限的集合,无涯教程使用常规的createCollection命令,但将capped选项......