context.Context
context.Context
Deadlinecontext.ContextDoneDoneErrcontext.ContextDoneValuecontext.ContextValueKey
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
Go
contextcontext.Backgroundcontext.TODOcontext.WithDeadlinecontext.WithValue
6.1.1 设计原理 #
context.Context
context.Context
图 6-1 Context 与 Goroutine 树
context.Contextcontext.Context
图 6-2 不使用 Context 同步信号
context.Context
图 6-3 使用 Context 同步信号
context.Contexthandle
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
go handle(ctx, 500*time.Millisecond)
select {
case <-ctx.Done():
fmt.Println("main", ctx.Err())
}
}
func handle(ctx context.Context, duration time.Duration) {
select {
case <-ctx.Done():
fmt.Println("handle", ctx.Err())
case <-time.After(duration):
fmt.Println("process request with", duration)
}
}
Go
因为过期时间大于处理时间,所以我们有足够的时间处理该请求,运行上述代码会打印出下面的内容:
$ go run context.go
process request with 500ms
main context deadline exceeded
Go
handleselectmainselectcontext.Contextmain context deadline exceeded
如果我们将处理请求时间增加至 1500ms,整个程序都会因为上下文的过期而被中止,:
$ go run context.go
main context deadline exceeded
handle context deadline exceeded
Go
context.Contextctx.Done()
6.1.2 默认上下文 #
contextcontext.Backgroundcontext.TODObackgroundtodo
func Background() Context {
return background
}
func TODO() Context {
return todo
}
Go
new(emptyCtx)context.emptyCtx
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
}
Go
context.emptyCtxcontext.Context
图 6-4 Context 层级关系
context.Backgroundcontext.TODO
context.Background
6.1.3 取消信号 #
context.WithCancelcontext.Context
图 6-5 Context 子树的取消
context.WithCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
Go
func propagateCancel(parent Context, child canceler) {
done := parent.Done()
if done == nil {
return // 父上下文不会触发取消信号
}
select {
case <-done:
child.cancel(false, parent.Err()) // 父上下文已经被取消
return
default:
}
if p, ok := parentCancelCtx(parent); ok {
p.mu.Lock()
if p.err != nil {
child.cancel(false, p.err)
} else {
p.children[child] = struct{}{}
}
p.mu.Unlock()
} else {
go func() {
select {
case <-parent.Done():
child.cancel(false, parent.Err())
case <-child.Done():
}
}()
}
}
Go
上述函数总共与父上下文相关的三种不同的情况:
parent.Done() == nilparentchildparentchildchildparentchildrenparentcontext.ContextDone()parent.Done()child.Done()parent.Done()child.cancel
context.propagateCancelparentchildparentchild
context.cancelCtxcontext.cancelCtx.cancel
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return
}
c.err = err
if c.done == nil {
c.done = closedchan
} else {
close(c.done)
}
for child := range c.children {
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c)
}
}
Go
context.WithCancelcontextcontext.WithDeadlinecontext.WithTimeoutcontext.timerCtx
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
return WithDeadline(parent, time.Now().Add(timeout))
}
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
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) // 已经过了截止日期
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) }
}
Go
context.WithDeadlinecontext.timerCtxtime.AfterFunccontext.timerCtx.cancel
context.timerCtxcontext.cancelCtxtimerdeadline
type timerCtx struct {
cancelCtx
timer *time.Timer // Under cancelCtx.mu.
deadline time.Time
}
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
return c.deadline, true
}
func (c *timerCtx) cancel(removeFromParent bool, err error) {
c.cancelCtx.cancel(false, err)
if removeFromParent {
removeChild(c.cancelCtx.Context, c)
}
c.mu.Lock()
if c.timer != nil {
c.timer.Stop()
c.timer = nil
}
c.mu.Unlock()
}
Go
context.timerCtx.cancelcontext.cancelCtx.cancel
6.1.4 传值方法 #
contextcontext.WithValuecontext.valueCtx
func WithValue(parent Context, key, val interface{}) Context {
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
Go
context.valueCtxValueErrDeadlinecontext.valueCtx.Value
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
Go
context.valueCtxcontext.valueCtx.Valuenil
6.1.5 小结 #
context.Context
context.Context