Golang学习 - 上下文(context.Context类型)
hanpy

context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等

context接口

1
2
3
4
5
6
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}

Deadline 方法的第一个返回值表示还有多久到期,第二个返回值 表示是否到期
Done 方法返回一个通道,可以监听该通道的信号,如果收到信号则表示通道已经关闭,需要执行退出
Err 方法会返回退出的原因
value 方法返回指定key对应的value,这是context携带的值

默认的上下文

context包中最常用的方法还是context.Backgroundcontext.TODO,这两个方法都会返回预先初始化好的私有变量backgroundtodo

go/src/context/context.go)

1
2
3
4
5
6
7
8
9
10
11
12
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)

func Background() Context {
return background
}

func TODO() Context {
return todo
}

这两个私有变量都是通过new(emptyCtx)语句初始化的,它们是指向私有结构体context.emptyCtx的指针

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
type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
return
}

func (*emptyCtx) Done() <-chan struct{} {
return nil
}

func (*emptyCtx) Err() error {
return nil
}

func (*emptyCtx) Value(key interface{}) interface{} {
return nil
}

func (e *emptyCtx) String() string {
switch e {
case background:
return "context.Background"
case todo:
return "context.TODO"
}
return "unknown empty Context"
}

emptyCtx什么内容都没有,其不可以被退出,也不能携带值。
context.Background是上下文的默认值,所有其他的上下文都应该从它衍生出来,如果当前函数没有上下文作为入参,我们都会使用 context.Background 作为起始的上下文向下传递
context.TODO应该仅在不确定应该使用哪种上下文时使用

WithCancel

context.WithCancel 函数能够从 context.Context 中衍生出一个新的子上下文并返回用于取消该上下文的函数。一旦我们执行返回的取消函数,当前上下文以及它的子上下文都会被取消,所有的 Goroutine 都会同步收到这一取消信号。

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
func main() {
// 生成一个可以取消的Context
ctx, cancelFunc := context.WithCancel(context.Background())
go Op(ctx)
// 3s之后取消主协程
time.Sleep(time.Second * 3)
cancelFunc()
// 等待子协程输出信息
time.Sleep(time.Second)
fmt.Println("main stop")
}

func Op(ctx context.Context) {
for {
select {
default:
time.Sleep(time.Second)
fmt.Println("Op run")
case <-ctx.Done():
fmt.Println("parent stop")
return
}
}
}

//Op run
//Op run
//Op run
//parent stop
//main stop

WithTimeout

context.WithTimeout 函数接收父上下文(parent)和一个超时时间,能够从 context.Context 中衍生出一个新的子上下文并返回用于取消该上下文的函数。执行取消函数时功能和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
func main()  {
// 基于context.Background()为根,创建一个3秒后自动取消的子Context
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*3)
defer cancelFunc()
go Op(ctx)
// 等待超时,自动取消
<-ctx.Done()
// 等待子协程输出信息
time.Sleep(time.Second)
fmt.Println("main stop")
}

func Op(ctx context.Context) {
for {
select {
default:
time.Sleep(time.Second)
fmt.Println("Op run")
case <-ctx.Done():
fmt.Println("parent stop")
return
}
}
}

WithDeadline

context.WithDeadline 函数接收父上下文(parent)和一个截止时间,能够从 context.Context 中衍生出一个新的子上下文并返回用于取消该上下文的函数。执行取消函数时功能和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
func main()  {
// 基于context.Background()为根,创建一个5秒后自动取消的子Context
ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(time.Second*5))
defer cancelFunc()
go Op(ctx)
// 5s后自动取消
<-ctx.Done()
// 等待子协程输出信息
time.Sleep(time.Second)
fmt.Println("main stop")
}

func Op(ctx context.Context) {
for {
select {
default:
time.Sleep(time.Second)
fmt.Println("Op run")
case <-ctx.Done():
fmt.Println("parent stop")
return
}
}
}

WithValue

context.WithValue 能从父 Context中创建一个子子 Context,并传体一个键值对信息给子 Context,在子 Context中,通过context.Value获取对应的值信息。

1
2
3
4
5
6
7
8
func main() {
valueCtx := context.WithValue(context.Background(), "name", "hanpy")
go func(ctx context.Context) {
fmt.Printf("取出上下文中的name: %v\n",ctx.Value("name"))
}(valueCtx)
time.Sleep(time.Second)
fmt.Println("main stop")
}