3 Context Tree
在实际实现中,我们通常使用派生上下文。我们创建一个父上下文并将其传递到一个层,我们派生一个新的上下文,它添加一些额外的信息并将其再次传递到下一层,依此类推。通过这种方式,我们创建了一个从作为父级的根上下文开始的上下文树。这种结构的优点是我们可以一次性控制所有上下文的取消。如果根信号关闭了上下文,它将在所有派生的上下文中传播,这些上下文可用于终止所有进程,立即释放所有内容。这使得上下文成为并发编程中非常强大的工具。
4 创建上下文
4.1 上下文创建函数
我们可以从现有的上下文中创建或派生上下文。顶层(根)上下文是使用 Background
或 TODO
方法创建的,而派生上下文是使用 WithCancel、WithDeadline、WithTimeout 或 WithValue 方法创建的。
所有派生的上下文方法都返回一个取消函数 CancelFunc,但 WithValue 除外,因为它与取消无关。调用 CancelFunc 会取消子项及其子项,删除父项对子项的引用,并停止任何关联的计时器。调用 CancelFunc 失败会泄漏子项及其子项,直到父项被取消或计时器触发。
context.Background() ctx Context
此函数返回一个空上下文。这通常只应在主请求处理程序或顶级请求处理程序中使用。这可用于为主函数、初始化、测试以及后续层或其他 goroutine 派生上下文的时候。
ctx, cancel := context.Background()
context.TODO() ctx Context
此函数返回一个非 nil 的、空的上下文。没有任何值、不会被 cancel,不会超时,也没有截止日期。但是,这也应该仅在您不确定要使用什么上下文或者该函数还不能用于接收上下文时,可以使用这个方法,并且将在将来需要添加时使用。
ctx, cancel := context.TODO()
context.WithValue(parent Context, key, val interface{}) Context
这个函数接受一个上下文并返回一个派生的上下文,其中值 val 与 key 相关联,并与上下文一起经过上下文树。
WithValue
方法其实是创建了一个类型为 valueCtx 的上下文,它的类型定义如下:
type valueCtx struct { Context key, val interface{} }
这意味着一旦你得到一个带有值的上下文,任何从它派生的上下文都会得到这个值。该值是不可变的,因此是线程安全的。
提供的键必须是可比较的,并且不应该是字符串类型或任何其他内置类型,以避免使用上下文的包之间发生冲突。 WithValue 的用户应该为键定义自己的类型。为避免在分配给 interface{}
时进行分配,上下文键通常具有具体类型 struct{}
。或者,导出的上下文键变量的静态类型应该是指针或接口。
package main import ( "context" "fmt" ) type contextKey string func main() { var authToken contextKey = "auth_token" ctx := context.WithValue(context.Background(), authToken, "Hello123456") fmt.Println(ctx.Value(authToken)) }
运行该代码:
$ go run . Hello123456
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
此函数接收父上下文并返回派生上下文,返回 parent 的副本,只是副本中的 Done Channel 是新建的对象,它的类型是 cancelCtx。在这个派生上下文中,添加了一个新的 Done
channel,该 channel 在调用 cancel
函数或父上下文的 Done 通道关闭时关闭。
要记住的一件事是,我们永远不应该在不同的函数或层之间传递这个 cancel
,因为它可能会导致意想不到的结果。创建派生上下文的函数应该只调用取消函数。
下面是一个使用 Done 通道演示 goroutine 泄漏的示例:
package main import ( "context" "fmt" "math/rand" "time" ) func main() { rand.Seed(time.Now().UnixNano()) ctx, cancel := context.WithCancel(context.Background()) defer cancel() for char := range randomCharGenerator(ctx) { generatedChar := string(char) fmt.Printf("%v\n", generatedChar) if generatedChar == "o" { break } } } func randomCharGenerator(ctx context.Context) <-chan int { char := make(chan int) seedChar := int('a') go func() { for { select { case <-ctx.Done(): fmt.Printf("found %v", seedChar) return case char <- seedChar: seedChar = 'a' + rand.Intn(26) } } }() return char }
运行结果:
$ go run . a m q c l t o