(转)golang中context包解读

2019-02-22  本文已影响0人  one_zheng

源码剖析

context.Context 接口

context包的核心

// context 包里的方法是线程安全的,可以被多个 goroutine使用
type Context interface {
  // 如果存在,Dealline 返回Context将要关闭的时间
  Deadline() (deadline time.Time, ok bool) 

  // 当Context 被 canceled 或是 times out 的时候,Done 返回一个被 closed 的Channel
  Done() <-chan struct{}

  // 在 Done 的 channel被closed后, Err 代表被关闭的原因
  Err() error

  // 如果存在,Value 返回与 key 相关了的值, 不存在则返回 nil
  Value(key interface{}) interface{}
}

  我们不需要手动实现这个接口,context 包已经给我们提供了两个,一个是Background(),y一个是TODO(),这两个函数都会返回一个Context的实例,只是返回的这两个实例都是 空Context.

package context

var (
    background = new(emptyCtx)
    todo = new(emptyCtx)
)

// Background returns a non-nil, empty Context.
// 该Context从不canceled,不存在values,且没有deadline时间.
// 经常用于main方法,context的初始化,和测试,或者用于最基类context用于接受
func Background() Context {
  return background
}

// TODO returns a non-nil, empty Context.
// 开发人员可以在不清楚context的使用场景和尚未调用context时使用该方法(因为它的function还未因接受context参数而实现).TODO可以由静态分析工具识别是否符合程序的正确性. 
func TODO() Context {
  return todo
}

主要结构

  cancelCtx结构体继承了Context,实现了canceler方法:

//  canceler 是一个可以被关闭的context.
type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chane struct {}
}

// closedchan is a reusable closed channel.
var closechan = make(chan struct{})


func init() {
  close(closechan)
}

// *cancelCtx和*timerCtx都实现了canceler接口,实现该接口的类型都可以被直接canceled
type cancelCtx struct {
  Context
  mu sync.Mutex
  done chan struct{}  // closed by the first cancel call.
  children map[canceler]bool // set to nil by the first call
  err error // 当其被cancel时将会把err设置为非nil
}

func (c *cancelCtx) Done() <-chan struct{} {
  return c.done
}


func (c *cancelCtx) Err() error {
  c.mu.Lock()
  defer c.mu.Unlock()
  return c.err
}

func (c *cancelCtx) String() string {
  return fmt.Sprintf("%v.WithCancel", c.Context)
}

// 核心是关闭c.done
// 同时会设置c.err = err, c.children = nil
// 依次遍历c.children,每个child分别cancel
// 如果设置了removeFromParent,则将c从其parent的
children中删除

func (c *cancelCtx) cancel(removeFromParent bool, err error) {
  if err == nil {
    panic("context: internal error: missing cancel error")
  }
  c.mu.Lock()
  if c.err != nil {
    c.mu.Unlock()
    return // already canceled
  }
  c.err = err 
  if c.done == nil {
     c.done = closechan
  } else {
      close(c.done)
  }
  for child := range c.children {
    // NOTE: acquiring the child {
      child.cancel(false, err)
 }
c.children = nil
c.mu.Unlock()

if removeFromParent {
  removeChild(c.Context, c)
}
}

timerCtx 结构继承 cancelCtx

// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
// implement Done and Err. It implements cancel by stopping its timer then
// delegating to cancelCtx.cancel.
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) String() string {
  return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, time.Until(c.deadline))
}

func(c *timerCtx) cancel(removeFromParent bool,err error) {
  c.cancelCtx.cancel(false, err)
  if removeFromParent {
    // Remove this timerCtx from its parent cancelCtx's children.
     removeChild(c.cancelCtx.context,c)
  }
  c.mu.Lock()
  if c.timer != nil {
    c.timer.Stop()
    c.timer = nil
  }
  c.mu.Unlock()
}

valueCtx 结构继承 cancelCtx

// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
    Context
    key, val interface{}
}

func (c *valueCtx) String() string {
    return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
}

func (c *valueCtx) Value(key interface{}) interface{} {
    if c.key == key {
        return c.val
    }
    return c.Context.Value(key)
}

主要方法

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key interface{}, val interface{}) Context

WithCancel 对应的是 cancelCtx ,其中,返回一个 cancelCtx ,同时返回一个 CancelFunc,CancelFunc 是 context 包中定义的一个函数类型:type CancelFunc func()。调用这个 CancelFunc 时,关闭对应的c.done,也就是让他的后代goroutine退出。

WithDeadlineWithTimeout 对应的是 timerCtx ,WithDeadline 和 WithTimeout 是相似的,WithDeadline 是设置具体的 deadline 时间,到达 deadline 的时候,后代 goroutine 退出
WithTimeout 简单粗暴,直接 return WithDeadline(parent, time.Now().Add(timeout))。
WithValue 对应 valueCtx ,WithValue 是在 Context 中设置一个 map,拿到这个 Context 以及它的后代的 goroutine 都可以拿到 map 里的值。

使用原则

使用示例

package main

import (
    "context"
    "fmt"
    "time"
)

// 模拟一个最小执行时间的阻塞函数
func inc(a int) int {
    res := a + 1                // 虽然我只做了一次简单的 +1 的运算,
    time.Sleep(1 * time.Second) // 但是由于我的机器指令集中没有这条指令,
    // 所以在我执行了 1000000000 条机器指令, 续了 1s 之后, 我才终于得到结果。B)
    return res
}

// 向外部提供的阻塞接口
// 计算 a + b, 注意 a, b 均不能为负
// 如果计算被中断, 则返回 -1
func Add(ctx context.Context, a, b int) int {
    res := 0
    for i := 0; i < a; i++ {
        res = inc(res)
        select {
        case <-ctx.Done():
            return -1
        default:
        }
    }
    for i := 0; i < b; i++ {
        res = inc(res)
        select {
        case <-ctx.Done():
            return -1
        default:
        }
    }
    return res
}

func main() {
    {
        // 使用开放的 API 计算 a+b
        a := 1
        b := 2
        timeout := 2 * time.Second
        ctx, _ := context.WithTimeout(context.Background(), timeout)
        res := Add(ctx, 1, 2)
        fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)
    }
    {
        // 手动取消
        a := 1
        b := 2
        ctx, cancel := context.WithCancel(context.Background())
        go func() {
            time.Sleep(2 * time.Second)
            cancel() // 在调用处主动取消
        }()
        res := Add(ctx, 1, 2)
        fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)
    }
}

输出:
Compute: 1+2, result: -1
Compute: 1+2, result: -1
上一篇 下一篇

猜你喜欢

热点阅读