赞
踩
Go语言中的context
包定义了一个名为Context
的类型,它定义并传递截止日期、取消信号和其他请求范围的值,形成一个链式模型。如果我们查看官方文档,它是这样说的:
context
包定义了Context
类型,它在API边界和进程之间传递截止日期、取消信号和其他请求范围的值。
包中的主要实体是Context
本身,它是一个接口。它只有四个方法:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
这里,
Deadline
:返回应该取消上下文的时间,以及一个布尔值,当没有截止日期时为falseDone
:返回一个只接收的空结构体通道,它发出上下文应该被取消的信号Err
:在完成通道打开时返回nil;否则它返回上下文取消的原因Value
:返回与当前上下文的某个键关联的值,如果没有该键的值,则返回nil与标准库的其他接口相比,Context
有许多方法,这些接口通常只有一两个方法。其中三个密切相关:
Deadline
是取消的时间Done
信号上下文完成时Err
返回取消的原因最后一个方法,Value
,返回与某个键关联的值。包的其余部分是一系列函数,允许你创建不同类型的上下文。
为什么使用Context
?
Context
会使你的代码清晰易操纵,通过将所有进程以子父关系链式连接,你可以将任何进程绑定/连接在一起。Background
是一个空的上下文,它不会被取消,没有截止日期,也不持有任何值。它主要由主函数用作根上下文或用于测试目的:
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.Background()
fmt.Println(ctx)
}
输出:
$ go run main.go
context.Background
输出是“context.Background”,它告诉我们这是一个空的Context
,它是一个接口,在这个Context
接口上,
所有这些数据目前都是nil或空的,因为我们有一个空的Context
,即背景上下文,它永远不会被取消,没有截止日期,也没有值。Background
通常用于main、init和测试中,以及作为传入请求的顶级上下文。
让我们对所有这些进行fmt.Println
检查:
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.Background()
fmt.Println("ctx.Err() : ", ctx.Err())
fmt.Println("ctx.Done() : ", ctx.Done())
fmt.Println("ctx.Value(\"key\") : ", ctx.Value("key"))
fmt.Print("ctx.Deadline() : ")
fmt.Print(ctx.Deadline())
}
输出:
$ go run main.go
ctx.Err() : <nil>
ctx.Done() : <nil>
ctx.Value("key") : <nil>
ctx.Deadline() : 0001-01-01 00:00:00 +0000 UTC false
Go语言的Context
包被用来处理我们的进程或API中的请求流,通过将子上下文与父上下文链接起来,我们可以使用context.WithDeadline
或context.WithTimeout
方法在链中控制截止日期和取消信号。
Context的底层是无法改变的,他在main函数创建,之后传递给其他子函数,比如goroutine,子函数无法改变context,也无法被子进程取消
与done channel不同的是,done是以关闭让其他关闭,而context中的cancel函数则是被调用的
父函数可以取消子函数
如果子函数创建了自己的子函数,也可以把这个context传递下去
那如果子函数想取消自己的子函数呢?
我们可以创建新的context,基于旧的context
我们来举个例子:
func main() { // 初始化context var wg sync.WaitGroup ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 添加数据 generator := func(data string, stream chan any) { for { select { case <-ctx.Done(): return case stream <- data: } } } infiniteApples := make(chan any) go generator("apple", infiniteApples) infiniteBananas := make(chan any) go generator("banana", infiniteBananas) infiniteOranges := make(chan any) go generator("orange", infiniteOranges) wg.Add(1) go func1(ctx, &wg, infiniteApples) func2 := genericFunc func3 := genericFunc wg.Add(1) go func2(ctx, &wg, infiniteBananas) wg.Add(1) go func3(ctx, &wg, infiniteOranges) wg.Wait() } func func1(ctx context.Context, s *sync.WaitGroup, streams <-chan any) { defer s.Done() var wg sync.WaitGroup doWOrks := func(CTX context.Context) { defer wg.Done() for { select { case <-CTX.Done(): return case d, ok := <-streams: if !ok { fmt.Println("stream closed") return } fmt.Println(d) } } } // 基于父上下文设置自己的上下文 newCtx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() // 启动自己的子函数 for i := 0; i < 3; i++ { wg.Add(1) go doWOrks(newCtx) } wg.Wait() } // 沿用父函数的context func genericFunc(ctx context.Context, s *sync.WaitGroup, apples chan any) { defer s.Done() for { select { case <-ctx.Done(): return case d, ok := <-apples: if !ok { fmt.Println("stream closed") return } fmt.Println(d) } } }
在输出过程中,我们会发现apple先停止输出,最后5秒到了,另外两个goroutine也停止运行 。
在Go语言中,context
上下文可以通过WithValue
函数来传递值。这个函数接受一个父上下文(parent Context)、一个键(key)和一个值(value),返回一个新的上下文(Context),这个新上下文与父上下文相同,但是增加了一个键值对。这个键值对可以在上下文的整个传递链中被检索。
以下是一个使用WithValue
传递值的例子:
package main import ( "context" "fmt" ) func main() { // 创建一个带有值的上下文 ctx := context.WithValue(context.Background(), "language", "Go") // 传递ctx到函数中 process(ctx) } func process(ctx context.Context) { // 从ctx中检索值 if language, ok := ctx.Value("language").(string); ok { fmt.Println("Language:", language) } }
在这个例子中,我们创建了一个带有键"language"
和值"Go"
的上下文。然后,我们将这个上下文传递给了process
函数,在这个函数中,我们检索并打印出了这个值。
需要注意的是,context
的值应该是请求范围的数据,而不是全局的。它通常用于传递请求相关的元数据,如请求ID、用户身份信息等。此外,由于context
是并发安全的,所以它可以在多个goroutine之间安全地传递和使用。
context.Context
时,确保它总是作为第一个参数。context.Context
。context.WithValue
。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。