
为此Go1.7以来提供了 context 来解决类似的问题 , context 可以跟踪 Goroutine 的调用, 在调用内部维护一个调用树,通过这个调用树可以在传递超时或者退出通知,还能在调用树中传递元数据
context的中文翻译是上下文 ,我们可以理解为 context 管理了一组呈现树状结构的 Goroutine ,让每个Goroutine 都拥有相同的上下文,并且可以在这个上下文中传递数据
context 实际上只是定义的4个方法的接口,凡是实现了该接口的都称为一种 context
|
|
// A Context carries a deadline, a cancelation signal, and other values across // API boundaries. // // Context's methods may be called by multiple goroutines simultaneously. type Context interface { // 标识deadline是否已经设置了,没有设置时,ok的值是false,并返回初始的time.Time Deadline() (deadline time.Time, ok bool) // 返回一个channel, 当返回关闭的channel时可以执行一些操作 Done() <-chan struct{} // 描述context关闭的原因,通常在Done()收到关闭通知之后才能知道原因 Err() error // 获取上游Goroutine 传递给下游Goroutine的某些数据 Value(key interface{}) interface{} } |
context.go 包中提供了4个以 With 开头的函数, 这几个函数的主要功能是实例化不同类型的context
通过 Background() 和 TODO() 创建最 emptyCtx 实例 ,通常是作为根节点
通过 WithCancel() 创建 cancelCtx 实例
通过 WithValue() 创建 valueCtx 实例
通过 WithDeadline 和 WithTimeout 创建 timerCtx 实例
3.1 WithCancel使用示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
package main import ( "context" "fmt" "time" ) func MyOperate1(ctx context.Context) { for { select { default: fmt.Println("MyOperate1", time.Now().Format("2006-01-02 15:04:05")) time.Sleep(2 * time.Second) case <-ctx.Done(): // 这个ctx cancel执行 fmt.Println("MyOperate1 Done") return } } } func MyOperate2(ctx context.Context) { fmt.Println("Myoperate2") } func MyDo2(ctx context.Context) { go MyOperate1(ctx) go MyOperate2(ctx) for { select { default: fmt.Println("MyDo2 : ", time.Now().Format("2006-01-02 15:04:05")) time.Sleep(2 * time.Second) case <-ctx.Done(): fmt.Println("MyDo2 Done") return } } } func MyDo1(ctx context.Context) { go MyDo2(ctx) for { select { case <-ctx.Done(): fmt.Println("MyDo1 Done") // 打印 ctx 关闭原因 fmt.Println(ctx.Err()) return default: fmt.Println("MyDo1 : ", time.Now().Format("2006-01-02 15:04:05")) time.Sleep(2 * time.Second) } } } func main() { // 创建 cancelCtx 实例 // 传入context.Background() 作为根节点 ctx, cancel := context.WithCancel(context.Background()) // 向协程中传递ctx go MyDo1(ctx) time.Sleep(5 * time.Second) fmt.Println("stop all goroutines") // 执行cancel操作 cancel() time.Sleep(2 * time.Second) } |
3.2 WithDeadline
设置了deadline的context
这个deadline(最终期限) 表示context在指定的时刻结束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
package main import ( "context" "fmt" "time" ) func dl2(ctx context.Context) { n := 1 for { select { case <-ctx.Done(): fmt.Println("dl2", ctx.Err()) return default: fmt.Println("dl2 : ", n) n++ time.Sleep(time.Second) } } } func dl1(ctx context.Context) { n := 1 for { select { case <-ctx.Done(): fmt.Println("dl1", ctx.Err()) return default: fmt.Println("dl1 : ", n) n++ time.Sleep(2 * time.Second) } } } func main() { // 设置deadline为当前时间之后的5秒那个时刻 d := time.Now().Add(5 * time.Second) ctx, cancel := context.WithDeadline(context.Background(), d) defer cancel() go dl1(ctx) go dl2(ctx) for { select { case <-ctx.Done(): fmt.Println("over", ctx.Err()) <strong><span style="color: #ff0000;"> //time.Sleep(3 * time.Second) 加上这一行才能看到子goruntine的println,说明父ctx先结束</span></strong> return } } } |
运行结果为:over context deadline exceeded
3.3 WithTimeout
实际就是调用了WithDeadline()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
package main import ( "context" "fmt" "time" ) func to1(ctx context.Context) { n := 1 for { select { case <-ctx.Done(): fmt.Println("to1 is over") return default: fmt.Println("to1 : ", n) n++ time.Sleep(time.Second) } } } func main() { // 设置为6秒后context结束 ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second) defer cancel() go to1(ctx) n := 1 for { select { case <-time.Tick(2 * time.Second): if n == 9 { return } fmt.Println("number :", n) n++ } } } |
3.4 WithValue
仅是在Context 基础上添加了 key : value 的键值对
context 形成的树状结构,后面的节点可以访问前面节点传导的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
package main import ( "context" "fmt" "time" ) func v3(ctx context.Context) { for { select { case <-ctx.Done(): fmt.Println("v3 Done : ", ctx.Err()) return default: fmt.Println(ctx.Value("key")) time.Sleep(3 * time.Second) } } } func v2(ctx context.Context) { fmt.Println(ctx.Value("key")) fmt.Println(ctx.Value("v1")) // 相同键,值覆盖 ctx = context.WithValue(ctx, "key", "modify from v2") go v3(ctx) } func v1(ctx context.Context) { if v := ctx.Value("key"); v != nil { fmt.Println("key = ", v) } ctx = context.WithValue(ctx, "v1", "value of v1 func") go v2(ctx) for { select { default: fmt.Println("print v1") time.Sleep(time.Second * 2) case <-ctx.Done(): fmt.Println("v1 Done : ", ctx.Err()) return } } } func main() { ctx, cancel := context.WithCancel(context.Background()) // 向context中传递值 ctx = context.WithValue(ctx, "key", "main") go v1(ctx) time.Sleep(10 * time.Second) cancel() time.Sleep(3 * time.Second) } |
来源:https://blog.csdn.net/weixin_37717557/article/details/106949500
只抄了应用,来源里面还有原理
注意go的context与hyperf的有很大区别,hyperf少很多东西,几乎只能当做一个协程内数组?这也是刚开始不理解go context原因,先入为主了
https://hyperf.wiki/2.2/#/zh-cn/coroutine
「三年博客,如果觉得我的文章对您有用,请帮助本站成长」
共有 0 - Golang 之context用法