概述
context
是 go 语言中非常重要的一个概念,它主要用于跨多个函数或 goroutine 传递 取消信号、超时控制、截止时间 和 请求范围数据。
在并发编程中,context
提供了更好的控制和管理,尤其是当你需要在多个 goroutine 之间传递状态或进行资源清理时。
主要功能
context
主要有以下几个功能:
- 取消信号:通知一个或多个 goroutine 取消它们正在执行的工作。
- 超时和截止时间:指定操作的最大执行时间,防止阻塞操作过长时间。
- 传递请求范围数据:携带请求范围内的数据,通常用于请求 id、用户信息等。
context 的三种基本类型
go 中的 context
包提供了几种常用的 context
类型:
context.background()
:通常作为根context
,表示没有附加数据或取消信号的上下文。它通常是根上下文,作为其他上下文的父上下文。context.todo()
:表示你暂时没有确定使用什么样的context
,通常用于占位。context.withcancel(parent)
:创建一个可取消的context
,并返回一个取消函数,当你调用这个函数时,context
会被取消。context.withtimeout(parent, timeout)
:创建一个带有超时的context
,指定最大等待时间,超过这个时间会自动取消。context.withdeadline(parent, deadline)
:指定一个具体的截止时间,超过这个时间后自动取消。context.withvalue(parent, key, value)
:创建一个携带键值对数据的context
,通常用于传递请求级别的数据(例如,用户身份信息)。
常见用法举例
context.withcancel传递取消信号
主要场景:
- 手动控制并发任务的终止。
- 优雅退出:在一个任务中途需要取消时,用
cancel()
通知所有相关的 goroutine 停止执行。
代码示例:
package main import ( "context" "fmt" "time" ) func main() { // 创建一个可取消的 context ctx, cancel := context.withcancel(context.background()) // 启动一个 goroutine,监听取消信号 go func(ctx context.context) { for { select { case <-ctx.done(): // 检测到取消信号 fmt.println("goroutine stopped") return default: // 模拟工作 fmt.println("working...") time.sleep(1 * time.second) } } }(ctx) // 主线程等待 3 秒后取消 time.sleep(3 * time.second) cancel() // 发送取消信号 // 等待 goroutine 退出 time.sleep(1 * time.second) fmt.println("main program exited") }
解释:
- 主线程创建了一个带有取消功能的上下文
ctx
。 - 子 goroutine 使用
ctx.done()
监听取消信号。 - 主线程 3 秒后调用
cancel()
,子 goroutine 检测到信号后优雅退出。
使用 withtimeout 设置超时
context.withtimeout
用于设置一个超时时间,超过该时间后 context
会自动取消,适用于需要限时执行的操作。防止某些任务阻塞的时间过长。
package main import ( "context" "fmt" "time" ) func main() { // 设置超时时间为 2 秒 ctx, cancel := context.withtimeout(context.background(), 2*time.second) defer cancel() // 确保超时后取消 ctx // 启动一个模拟长时间执行的任务 go longrunningtask(ctx) // 等待超时或任务完成 <-ctx.done() if ctx.err() == context.deadlineexceeded { fmt.println("timeout reached") } } func longrunningtask(ctx context.context) { select { case <-time.after(3 * time.second): // 模拟长时间任务 fmt.println("task completed") case <-ctx.done(): // 任务被取消或超时 fmt.println("task cancelled due to timeout") } }
withdeadline
的用法和withtimeout
用法类似,只是一个传入的参数是等待时间,一个传入的参数是截止时间。
使用 withvalue 传递数据
context.withvalue
可以在 context
中存储键值对,通常用于传递请求级别的数据(例如用户身份、请求 id 等)。
package main import ( "context" "fmt" ) func main() { // 创建一个上下文并传递数据 ctx := context.withvalue(context.background(), "userid", 12345) // 将 ctx 传递给其他函数 processrequest(ctx) } func processrequest(ctx context.context) { // 从 ctx 中提取数据 userid := ctx.value("userid") if userid != nil { fmt.println("user id:", userid) } else { fmt.println("no user id found") } }
使用 withvalue
小心:context.withvalue
并不是用于传递大量数据的,主要用于传递少量的上下文信息,比如请求 id 等。
如果传递过多的数据,会使得 context
难以维护。
常用的相关方法和常量
ctx.done()
:返回一个 channel,当context
被取消时该 channel 会被关闭。ctx.err()
:返回context
被取消的错误,通常是context.canceled
或context.deadlineexceeded
。ctx.value(key)
:获取在context
中传递的数据。
context如何控制goroutine的执行
从上面的举例可以看出,在每个goroutine中通过判断ctx.done()是否被执行,从而知道任务是否被取消/超时/到达截止日期。
当 context
被取消(调用 cancel()
)或超时/到达截止时间时,ctx.done()
所关联的 channel 会关闭,此时select语句就可以执行ctx.done()
对应的分支。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论