content 语句
Content(上下文),可以理解为一种维护Go协程的东西 —— 我们可以创建、运行一个Go协程,缺乏一种”维护“的机制,便引入了 Content。
它主要针对以下三种情况:
- 跨 goroutine 的取消信号与超时管理
- 请求级别的元数据(如 traceID、用户认证信息)传递
- 避免全局变量和隐式状态,保持函数签名清晰
假设有一个非常长的调用链,但是第一步出错了,正常情况下,Go协程会依旧把接下来的调用链给执行掉,然而这样非常浪费资源,以及可能会有其他问题(比如用户意外退出,但是数据库等没有关闭之类的)
将content
理解成一个带着特殊消息(取消/超时/完成/键值对 等)的只读接口,在各种调用链之类的中,可以将content
传入,并且显式的监听这些属性,并且做出对应的操作,就是content
使用
使用一个content
作为根节点,然后使用content
包下的一些函数来"拓展"它,最后使用。(其实最开始看到根节点还有点蒙到底是什么)
根节点
content.Background()
:返回一个空的、永不取消、无超时、无值的 Contextcontent.TODO()
:但语义上表示“还不确定用哪种 Context”
但是看源码定义:
go
type todoCtx struct{ emptyCtx }
type backgroundCtx struct{ emptyCtx }
在数据的结构上它们其实是一致的,只是表达的语义(阅读理解)上不一样。
Background
明确表示“顶层、永不取消”TODO
明确表示“待定、临时占位”
派生
“派生”,其实就是在根节点的Content
上加了点东西(嵌了下结构体)。
content.WithCancel
:提供一个cancel
函数,可以手动发送停止信号context.WithDeadline
:在指定的截止时间到达时自动取消子 Contextcontext.WithTimeout
:简化版的WithDeadline
—— 默认从现在开始time.Now().Add(timeout)
context.WithValue
:可以传递一个键值对,
观察源码,可以发现,就是一个工厂方法来负责从根节点派生,给调用链提供取消信号、超时控制或键值传递(就是对应不同使用场景下的content
)
实际例子:
GO
func worker(ctx context.Context) {
fmt.Printf("Start Working\n")
for {
select {
case <-ctx.Done():
fmt.Println("上下文发送退出信号, 退出, Err:", ctx.Err())
return
default:
fmt.Printf("Working...\n")
time.Sleep(300 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx)
time.Sleep(3 * time.Second)
cancel()
time.Sleep(500 * time.Millisecond)
fmt.Println("Main 结束")
}