一、简介
Context
用来简化 对于处理单个请求的多个 goroutine 之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个 API 调用。对服务器传入的请求应该创建上下文,而对服务器的传出调用应该接受上下文。它们之间的函数调用链必须传递上下文,或者可以使用 WithCancel
、WithDeadline
、WithTimeout
或WithValue
创建的派生上下文。当一个上下文被取消时,它派生的所有上下文也被取消。
二、使用示例
package main
import (
"fmt"
"time"
"sync"
"context"
)
var wg sync.WaitGroup
func f(ctx context.Context) {
defer wg.Done()
LOOP:
for {
fmt.Println("hello")
time.Sleep(time.Millisecond * 500)
select {
case <-ctx.Done():
break LOOP
default:
}
}
}
func main(){
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
go f(ctx)
time.Sleep(time.Second*5)
cancel()
wg.Wait()
}
三、接口
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
接口名 | 说明 |
Deadline | 返回当前Context 被取消的时间,也就是完成工作的截止时间 |
Done | 返回一个Channel ,这个Channel会在当前工作完成或者上下文被取消之后关闭,多次调用Done 方法会返回同一个Channel |
Err | 返回当前Context 结束的原因,只会在Done 返回的Channel被关闭时才会返回非空的值;被取消就会返回Canceled 错误;超时就会返回DeadlineExceeded 错误 |
Value | 从Context 中返回键对应的值,对于同一个上下文来说,多次调用Value 并传入相同的Key 会返回相同的结果,该方法仅用于传递跨API和进程间跟请求域的数据 |
1. Background()和TODO()
Background() | TODO() |
返回一个实现了Context 接口的background | 返回一个实现了Context 接口的todo |
主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Context | 不知道该使用什么Context的时候,可以使用这个 |
代码中最开始都是以这两个内置的上下文对象作为最顶层的partent context ,衍生出更多的子上下文对象,是一个不可取消,没有设置截止时间,没有携带任何值的Context。 |
2. WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
返回两个值,第一个是可撤销的Context值,第二个是用于触发撤销信号的函数。撤销函数被调用后,对应的Context值会先关闭它内部的接收通道,通道关闭了接收该通道的操作就会立即返回,就是Done方法返回的那个通道。然后,它还会向它的所有子值传达撤销信号。这些子值如果还有子值,就会一级一级把撤销信号传递下去。最后,这个Context值会断开它与其父值之间的关联。
3. WithDeadline 与 WithTimeout
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
通过调用WithDeadline函数或者WithTimeout函数生成的Context值也是可撤销的。它们不但可以被手动撤销,还会依据在生成是给定的过期时间,自动地进行定时撤销。这里的定时撤销功能是借助它们内部的计时器来实现的。当过期时间到达时,两种Context值的行为与手动撤销是的行为是几乎一致的,只是多了一步停止并释放掉内部的计时器。WithDeadline和WithTimeout是相似的。都是通过设置,会在某个时间自动触发,就是ctx.Done()能够取到值。差别是,DeadLine是设置一个时间点,时间对上了就到期。Timeout是设置一段时间,比如几秒,过个这段时间,就超时。其实底层的Timeout也是通过Deadlin实现的。
4. WithValue
func WithValue(parent Context, key, val interface{}) Context
WithValue函数需要3个参数:父值、键和值。这里键必须是可判断等的,类似字典的键。不过Context值并不是用字典来存储键和值的,而是简单地存储在父值相应的字段中。通过Value方法,可以获取数据。在调用包含属性的Context值的Value方法时会先判断给定的键,如有有就返回存储的值,否则会到其父值中继续查找,会一直沿着上下文根节点的方法一直查找。因为其他几种Context值都是无法携带数据的,所以Value方法在查找的时候,会跨过这这些Context值。
Context接口没有提供改变数据的方法,所以通常只能通过在上下文数中添加含数据的Context值来存储新的数据,或者通过撤销此种值的父值丢弃掉相应的数据。如果存储在这里的数据可以从外部改变,那么必须自信保证安全。
返回值是不可撤销的,撤销信号在传播时,若遇到它们会直接跨过,并试图将信息直接传给它们的子值。