文章目录
Context 接口
type Context interface {
// 最后期限 返回期限时间 ok
Deadline() (deadline time.Time, ok bool)
// 返回一个channel。当times out或者调用cancel方法时,将会close掉。
Done() <-chan struct{}
// Context 被取消的原因 比如 timeout 、canceled
Err() error
//
Value(key interface{}) interface{}
}
context 包中包含如下几种衍生的子 Context
// 返回一个 可取消 Context
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
// 返回一个 在给定期限到期是 取消的 Context
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
// 返回一个 在 超时 时取消的 Context
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
// 该方法返回的 Context 与 “取消” 无关,它返回一个绑定了一个键值对数据的Context,用来传递上下文数据
func WithValue(parent Context, key, val interface{}) Context
使用 context.WithCancel 控制子 groutine 结束
一个网络请求 Request
,每个 Request
都需要开启一个goroutine
做一些事情,这些 goroutine
又可能会开启其他的 goroutine
。所以我们需要一种可以跟踪 goroutine
的方案,才可以达到控制他们的目的,这就是 Go 语言为我们提供的Context
,称之为上下文非常贴切,它就是 goroutine
的上下文。
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context){
for {
select {
case <-ctx.Done():
fmt.Println("主 goroutine 结束")
return
default:
fmt.Println("监控 goroutine 中...")
time.Sleep(time.Second)
}
}
}(ctx)
fmt.Println("主进程运行中。")
time.Sleep(3 * time.Second)
fmt.Println("主进程任务处理完成,停止监控。")
cancel()
time.Sleep(3 * time.Second)
}
context.Background()
返回一个空的 Context
(emptyCtx
),这个空的 Context
一般用于整个 Context
树的根节点。然后我们使用 context.WithCancel(parent)
函数,创建一个可取消的子 Context
(继承自context.Background()
),然后当作参数传给 goroutine
使用,这样就可以使用这个子 Context
跟踪这个 goroutine
。
在 goroutine
中,使用 select
调用 <-ctx.Done()
判断是否要结束,如果ctx.Done()
返回一个通道,该通道若关闭,就可以返回结束 goroutine
了;如果没有关闭,就会继续执行 default
分支。
那么是如何发送结束指令的呢?这就是示例中的 cancel
函数啦,它是我们调用context.WithCancel(parent)
函数生成子 Context
的时候返回的,第二个返回值就是这个取消函数,它是CancelFunc类型的。我们调用它就会关闭 ctx.done
(done 属性是一个无缓冲通道),然后我们的监控 goroutine
中的 selecrt
就会收到,返回结束。
通过 context.WithValue 来传值
func main() {
ctx, cancel := context.WithCancel(context.Background())
valueCtx := context.WithValue(ctx, "key", "add value")
go watch(valueCtx)
time.Sleep(10 * time.Second)
cancel()
time.Sleep(5 * time.Second)
}
func watch(ctx context.Context) {
for {
select {
case <-ctx.Done():
//get value
fmt.Println(ctx.Value("key"), "is cancel")
return
default:
//get value
fmt.Println(ctx.Value("key"), "int goroutine")
time.Sleep(2 * time.Second)
}
}
}
超时取消 context.WithTimeout
package main
import (
"fmt"
"time"
"context"
)
func work(ctx context.Context) error {
for i := 0; i < 1000; i++ {
select {
case <-time.After(1 * time.Second):
fmt.Println("Doing some work ", i)
// we received the signal of cancelation in this channel
case <-ctx.Done():
fmt.Println("Cancel the context ", i)
return ctx.Err()
}
}
return nil
}
func main() {
// 设置超时时间 4s
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
defer cancel()
fmt.Println("Hey, I'm going to do some work")
go work(ctx)
time.Sleep(6 * time.Second)
fmt.Println("Finished. I'm going home")
}
截止时间 context.WithTimeout 和 超时时间类似
控制多层 goroutine 结束
package main
import (
"context"
"fmt"
"time"
)
func main(){
t := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), t)
defer cancel()
fmt.Println("Start ...")
go parent(ctx)
select {
case <- time.After(6 * time.Second):
fmt.Println("End ...")
}
}
func parent(ctx context.Context) {
fmt.Println("\t[Goroutine 1] Goroutine 1 start...")
i := 0
for {
select {
case <- ctx.Done():
fmt.Println("\t[Goroutine 1] Goroutine 1 end ... \n")
return
default:
i++
fmt.Printf("\t[Goroutine 1] create a child processes -- %d\n", i)
vctx := context.WithValue(ctx, "id", i) // vctx 连接自 ctx, 当 ctx 被取消时 vctx 也会被取消
go child(vctx)
time.Sleep(time.Second)
}
}
}
func child(ctx context.Context) {
fmt.Printf("\t\t[Child Goroutine %d] Goroutine start...\n", ctx.Value("id"))
for {
select {
case <- ctx.Done():
fmt.Printf("\t\t[Child Goroutine %d] Goroutine end ...\n", ctx.Value("id"))
return
}
}
}
/ * 输出:
Start ...
[Goroutine 1] Goroutine 1 start...
[Goroutine 1] create a child processes -- 1
[Child Goroutine 1] Goroutine start...
[Goroutine 1] create a child processes -- 2
[Child Goroutine 2] Goroutine start...
[Goroutine 1] create a child processes -- 3
[Child Goroutine 3] Goroutine start...
[Goroutine 1] create a child processes -- 4
[Child Goroutine 4] Goroutine start...
[Goroutine 1] create a child processes -- 5
[Child Goroutine 5] Goroutine start...
[Child Goroutine 5] Goroutine end ...
[Child Goroutine 1] Goroutine end ...
[Child Goroutine 2] Goroutine end ...
[Child Goroutine 3] Goroutine end ...
[Child Goroutine 4] Goroutine end ...
[Goroutine 1] Goroutine 1 end ...
End ...
*/