抱歉,您的瀏覽器無法訪問本站
本頁面需要瀏覽器支持(啟用)JavaScript
了解詳情 >

前言

在Go的各种框架中,总是能看见context的身影,它被用来进行数据传递、超时通知等。

context在Golang1.7的版本被引入,它被称为协程的上下文,用来在各个协程之间进行上下文信息传递,例如:取消信号、超时信号、数据传递等。这种传递不仅只传送给被调用者、context能够进行链式的调用。

context接口

context被定义为一个接口,只有4个方法

1
2
3
4
5
6
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key any) any
}
  • Deadline:用于获得截止时间,当ok为true时,当到达deadline的时间点时,Context会自动发起取消请求,如果ok为false,则不会自动调用。
  • Done:该方法只返回一个只读的通道,同时它是无缓冲的通道,Context中也没有任何地方对他写入数据。因此当parent context要取消请求时,会关闭该通道,因为通道的特性,该通道就变成非读堵塞了。接受到done的信号后就可以进行情理操作。
  • Err:该方法返回取消的原因,如果Done方法的通道已被关闭就会返回对应的error,否则返回nil
  • Value:用于存储键值对的方法

Context类型

emptyContext

这只是一个空的Context,用做初始的的context,即父context。我们通常使用context.Background()返回该emptyContext类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 any) any {
return nil
}

cancelContext

这是一个用于取消通知的context,通常使用context.WithCancel()来返回该类型,该方法会返回cancelContext和cancel方法。

1
2
3
4
5
6
7
8
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}

当我们要关闭该context时,就会调用cancel方法,来通知其对应的子goroutine。

timerContext

该context跟cancelContext很想,只不过它是超时自动取消,通常我们使用context.WithTimeout()来返回该类型。

1
2
3
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}

ValueContext

该context就是用于存放键值对的,我们使用context.withValue()方法来返回。

1
2
3
4
5
6
7
8
9
10
11
12
func WithValue(parent Context, key, val any) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}

使用细节

关于context在平常的开发中需要注意几点

  1. 官方推荐将context作为方法参数放在第一位,而不是丢进结构体。(不过过多的context也不是太好)
  2. 如果一个函数方法需要传递context,但目前又不需要用到,就可以使用context.TODO()返回的结构体,不要直接传递nil
  3. 同时context是线程安全的,平常使用不用特意加锁