走去看看 Context 里面有什么
Cotext interface
首先,我们来看一下Context的接口中有哪些方法:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Deadline: Deadline 返回 context 的截至时间,当deadline 没有设置时, ok 设置为false
Done: 返回一个Channel对象, 在Context 被取消时, Channel 会被关闭,如果没有被取消,可能会返回 nil
Err: 如果 Done 没有被关闭了, Err返回 nil;如果 Done 被关闭了, Err 会返回被 close 的原因
Value:返回 context 中指定 key 的 value,如果 key 没有被指定则返回 nil
CancelFunc()
type CancelFunc func()
CancelFunc用来直接关闭一个 context, 一个 CancelFunc 可以被多个 goroutine 同时调用,再第一次调用后,后面调用它不再做任何事情。
Context 的起源
func Background()
Background 返回一个 non-nil, 空的 Contex, 它将永远不会被取消掉,没有 values 和 deadline,它被当作顶层上下文使用,通常被 main 函数使用,用于初始化和测试。
func TODO()
TODO 本质实现是和Background() 是一样的,官方给出的回答是在你不清楚要用哪一个 Context 的时候或者它没有需要到被使用的时候使用。
var (
background = new(emptyCtx)
todo = new(emptyCtx)
)
func Background() Context {
return background
}
func TODO() Context {
return todo
}
创建Context的四种方法
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
WithCancel 创建一个 parent 的副本,调用 CancelFunc 时,当前 context 会关闭。当 parent 关闭时时,当前 context 会跟着关闭。
func TestWithCancel(test *testing.T) {
ctx1, cancel1 := context.WithCancel(context.Background())
defer cancel1()
ctx2, cancel2 := context.WithCancel(ctx1)
defer cancel2()
fmt.Println("ctx1:", ctx1.Err())
fmt.Println("ctx2:", ctx2.Err())
cancel1()
fmt.Println("ctx1:", ctx1.Err())
fmt.Println("ctx2:", ctx2.Err())
}
//output
ctx1: <nil>
ctx2: <nil>
ctx1: context canceled
ctx2: context canceled
首先我们创建 ctx1, 创建以 ctx1 为父 context ctx2, 输出 ctx1.Err(), ctx2.Err() ,输出为 nil ,当前2个cxt 都没有被关闭,当我们 cancel1() 关闭 ctx1 时,输出发现 2 个 cxt 都已经被关闭。
方法实现:
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) }
}
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{Context: parent}
}
newCancelCtx 返回一个cancelCtx:
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done atomic.Value // of chan struct{}, created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
propagateCancel(parent, &c) 这个方法将父 context 和 子context做关联,子 context 保存在children 里面。
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
WithDeadline 返回一个 parent 的副本,并设置一个不晚于d的时间, 如果 parent 的截至时间比 d 早,则以 parent 的时间为准。
func TestWithDeadline(test *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(5*time.Second))
defer cancel()
for {
select {
case <-ctx.Done():
fmt.Println(ctx.Err())
return
case <-time.After(1 * time.Second):
fmt.Println("overslept")
}
}
}
//output
overslept
overslept
overslept
overslept
context deadline exceeded
首先,我们创建了一个5秒钟后结束 Context, 在select语句种, 我们接收到 ctx Done 关闭的信号输出Err的信息并退出,每过一秒钟输出overslept。
方法实现:
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
if parent == nil {
panic("cannot create context from nil parent")
}
// 如果parent的截至时间更早,直接创建parent的副本
if cur, ok := parent.Deadline(); ok && cur.Before(d) {
return WithCancel(parent)
}
c := &timerCtx{
cancelCtx: newCancelCtx(parent),
deadline: d,
}
propagateCancel(parent, c)
dur := time.Until(d)
if dur <= 0 {
c.cancel(true, DeadlineExceeded) // deadline has already passed
return c, func() { c.cancel(false, Canceled) }
}
c.mu.Lock()
defer c.mu.Unlock()
if c.err == nil {
c.timer = time.AfterFunc(dur, func() {
c.cancel(true, DeadlineExceeded)
})
}
return c, func() { c.cancel(true, Canceled) }
}
- 如果 parent 的是截至时间比当前 context 早,则直接用 WithCancel() 创建 parent 的副本
- propagateCancel() 绑定父子关系
- dur < 0 如果已经超过时间取消
- 最后执行一个定时任务,到截至时间后取消
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
WithTimeout 本身调用了WithDeadline,只不过一个参数是超时时间,一个参数是截至时间
func WithValue(parent Context, key, val interface{}) Context
ctx := context.Background()
ctx = context.WithValue(ctx, "key1", "123")
fmt.Println(ctx.Value("key1"))
如果 key 不存在, ctx 会从 parent 中去寻找。
注意事项:
当 parent 的 context 被关闭之后,所属的子context 都会被关闭