go的上下文(context)研究
引言
go的上下文官方说明有点难懂,可能是我太菜了,经过我自己的研究,总结了一下自己的想法
context上下文
顾名思义,context用于go代码传输上下文信息,例如在方法调用之间传递参数,传递栈信息等,另外可以通过context进行上下文控制.
它的最简单的使用方法为:
package main
import (
"context"
"fmt"
)
func main() {
baseCtx := context.Background()
stackArr := []string{}
stackArr = append(stackArr, "main")
ctx := context.WithValue(baseCtx,"stack", stackArr)
test(ctx)
}
func test(ctx context.Context) {
var stackArr []string
stackInterface := ctx.Value("stack")
for _,val :=range stackInterface.([]string){
stackArr = append(stackArr,val)
}
fmt.Printf("test Call stack:%v \n",stackArr)
stackArr = append(stackArr,"test")
ctx = context.WithValue(ctx,"stack",stackArr)
test2(ctx)
}
func test2(ctx context.Context) {
stackInterface := ctx.Value("stack")
fmt.Printf("test2 Call stack:%v \n",stackInterface)
}
输出:
可以看到,在main中 声明了一个baseCtx,然后传递了一个stack的字符串数组,到test方法时附加了一个新的值,test方法额外覆盖了这个值,重新将值附加到了test2,
可以看出:上下文其中的一个作用就是在调用栈中传递参数
context声明获取
context的结构体都基于 emptyCtx 类型(int),在正常使用时我们不会接触到这个类型,而是使用封装之后的 background,todo:
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
通过 Background(),Todo()方法进行获取一个上下文:
baseCtx := context.Background()
todoCtx := context.Todo()
要注意的是,
background和todo 返回的值没有任何区别,只是为了区分使用场景而使用了2个不同的方法和变量
background用于主函数初始化,测试中,在顶级调用栈时使用
而todo 用于不清楚要使用什么上下文的时候使用
context使用
上下文使用的步骤为:
1:声明一个基本context变量
2:通过context包的其他方法进行衍生赋值一个新的context,例如WithValue 方法
3:调用方法接收到context之后进行使用,例如Value方法
context的衍生方法
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
context的使用方法
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
-
Deadline:返回绑定当前context的任务被取消的截止时间;如果没有设定期限,将返回ok == false。
-
Done: 当绑定当前context的任务被取消时,将返回一个关闭的channel;如果当前context不会被取消,将返回nil。
-
Err: 如果Done返回的channel没有关闭,将返回nil;如果Done返回的channel已经关闭,将返回非空的值表示任务结束的原因。如果是context被取消,Err将返回Canceled;如果是context超时,Err将返回DeadlineExceeded。
-
Value:返回context存储的键值对中当前key对应的值,如果没有对应的key,则返回nil。
withTimeout
该方法将返回一个带有超时时间的context,由下层接收该参数,通过监听done方法,如果超时时间到了,则会接收到超时消息(通过Done方法接收通道),如果上级主动调用Cancel方法,也会接收到消息
package main
import (
"context"
"fmt"
"time"
)
func main() {
baseCtx := context.Background()
timeoutCtx, cancel := context.WithTimeout(baseCtx, time.Second*5)
go test(timeoutCtx, "task 1")
go test(timeoutCtx, "task 2")
go test(timeoutCtx, "task 3")
time.Sleep(time.Second * 10)
cancel()
}
func test(ctx context.Context, taskName string) {
for {
select {
case <-ctx.Done():
fmt.Println(taskName, "received signal,monitor task exit,time=", time.Now().Unix())
return
default:
deadline,ok:=ctx.Deadline()
fmt.Println(taskName, "goroutine monitor,time=", time.Now().Unix(),"cancel time:",deadline.Unix(),ok)
time.Sleep(1 * time.Second)
}
}
}
输出:
WithValue
该方法将返回一个带有key->value的context,传递到其他方法可通过Value获取值
package main
import (
"context"
"fmt"
)
func main() {
baseCtx := context.Background()
stackArr := []string{}
stackArr = append(stackArr, "main")
ctx := context.WithValue(baseCtx,"stack", stackArr)
test(ctx)
}
func test(ctx context.Context) {
var stackArr []string
stackInterface := ctx.Value("stack")
for _,val :=range stackInterface.([]string){
stackArr = append(stackArr,val)
}
fmt.Printf("test Call stack:%v \n",stackArr)
stackArr = append(stackArr,"test")
ctx = context.WithValue(ctx,"stack",stackArr)
test2(ctx)
}
func test2(ctx context.Context) {
stackInterface := ctx.Value("stack")
fmt.Printf("test2 Call stack:%v \n",stackInterface)
}
输出:
WithDeadline
withDeadline作用跟WithTimeout类似,它需要传递一个最后运行时间,时间到了则超时:
timeoutCtx, cancel := context.WithDeadline(baseCtx, time.Now().Add(time.Duration(1)\*time.Second\*10))
WithCancel
withCancel的作用跟WithTimeout类似,但是取消了超时时间的控制,需要主动取消:
package main
import (
"context"
"fmt"
"time"
)
func main() {
baseCtx := context.Background()
timeoutCtx, cancel := context.WithCancel(baseCtx)
go test(timeoutCtx, "task 1")
go test(timeoutCtx, "task 2")
go test(timeoutCtx, "task 3")
time.Sleep(time.Second * 3)
cancel()
time.Sleep(time.Second * 3)
}
func test(ctx context.Context, taskName string) {
for {
select {
case <-ctx.Done():
fmt.Println(taskName, "received signal,monitor task exit,time=", time.Now().Unix())
return
default:
deadline,ok:=ctx.Deadline()
fmt.Println(taskName, "goroutine monitor,time=", time.Now().Unix(),"cancel time:",deadline.Unix(),ok)
time.Sleep(1 * time.Second)
}
}
}
输出:
context的作用
从上文可以看出,context最基本的作用为通过一个参数,连接协程/方法 栈的上下文,使其能够进行上下文通信
具体的应用场景有:
1:传递参数
2:超时控制,例如curl的时候控制超时时间
3:主动控制协程的退出
....
- 本文标签: 编程语言
- 本文链接: https://www.php20.cn/article/358
- 版权声明: 本文由仙士可原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权