Context包源码解析(附面经)

Context包源码解析(附面经)

Context包源码解析

Context就相当于一个树状结构

最后请回答一下这个问题:context包中的方法是线程安全吗?

Context包中主要有一个接口和三个结构体

Context接口

type Context interface {
	Deadline() (deadline time.Time, ok bool)
	Done() <-chan struct{}
	Err() error
	Value(key interface{}) interface{}
}

结构体

type valueCtx struct {
	Context
	key, val interface{}
}

type cancelCtx struct {
	Context

	mu       sync.Mutex            // protects following fields
	done     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
}

type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

Context包有两个根实例

package context
...
var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)

分别通过以下两个方法返回

其中Background()方法是返回初始化时自动实例化的background对象,TODO方法跟Background()相同

  • context.Background()

  • context.TODO()

func Background() Context {
	return background
}
TODO方法跟Background()相同
func TODO() Context {
	return todo
}

那么emptyCtx又是什么?

emptyCtx是一个自定义的类型,底层类型为int,实现了Context接口的四个方法,并都返回空值或初始值

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
}

func (e *emptyCtx) String() string {
	switch e {
	case background:
		return "context.Background"
	case todo:
		return "context.TODO"
	}
	return "unknown empty Context"
}

context包中常用的几个方法

  • 创建具有dealline的Context WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

  • 创建具有取消方法的Context WithCancel(parent Context) (ctx Context, cancel CancelFunc)

  • 创建具有超时的Context WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

  • 创建具有可保存键值的Context WithValue(parent Context, key, val interface{}) Context

WithValue(parent Context, key, val interface{}) Context

type valueCtx struct {
	Context //相当于父节点
	key, val interface{}
}

func WithValue(parent Context, key, val interface{}) 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接口中的 Value(key interface{})方法
func (c *valueCtx) Value(key interface{}) interface{} {
    //先从自己节点中的键对值去寻找
	if c.key == key {
		return c.val
	}
    //找不到就往上递归,依次寻找绑定的父节点的value
	return c.Context.Value(key)
}

写一个小demo验证一下

func main() {
	ctx := context.WithValue(context.Background(), "xiaofu", "test")
	ctx1 := context.WithValue(ctx, "xiaofu1", "test1")

	fmt.Println(ctx1.Value("xiaofu1"))
	fmt.Println(ctx1.Value("xiaofu"))
    
}
//输出
test1
test
<nil>
//说明是会往上递归,直到找到background的根节点

WithCancel(parent Context) (ctx Context, cancel CancelFunc)

查看以下代码,都会发现每次新建Context,都会绑定父节点的Context

type cancelCtx struct {
	Context //父节点

	mu       sync.Mutex            // 锁
	done     chan struct{}         // channel,用于关闭
	children map[canceler]struct{} // 用于储存子节点中的cancelCtx
	err      error                 // set to non-nil by the first cancel call
}

//重写了Value方法,当key为cancelCtxKey时,返回当前的cancelCtx,否则不断向上递归寻找cancelCtx
func (c *cancelCtx) Value(key interface{}) interface{} {
	if key == &cancelCtxKey {
		return c
	}
	return c.Context.Value(key)
}
//重写了Done方法
func (c *cancelCtx) Done() <-chan struct{} {
    //上锁
	c.mu.Lock()
    //初始化done的channel,根节点的Done()方法返回的是nil
	if c.done == nil {
		c.done = make(chan struct{})
	}
	d := c.done
    //解锁
	c.mu.Unlock()
	return d
}

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


var closedchan = make(chan struct{})

//用于控制取消操作的接口,其中因为cancelCtx实现了cancel方法和Done()方法,所以默认实现该接口
type canceler interface {
	cancel(removeFromParent bool, err error)
	Done() <-chan struct{}
}

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) }
}

//创建cancelCtx结构体,并绑定父节点
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {
	if parent == nil {
		panic("cannot create context from nil parent")
	}
	if cur, ok := parent.Deadline(); ok && cur.Before(d) {
		// The current deadline is already sooner than the new one.
		return WithCancel(parent)
	}
    //把当前的父节点用一个中间对象cancelCtx做转换,同时绑定到timeCtx中
    //约等于 temp := cancelCtx{Context: parent}
    //		c := &timerCtx{
    //			cancelCtx: temp,
    //			deadline: d,
	//		}
	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) }
}

WithTimeout(parent Context, timeout time.Duration)

//相当于在当前时间dealline的基础上,往后延迟一段时间,所以可以调用WithDeadline方法
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
  return WithDeadline(parent, time.Now().Add(timeout))
}

context包中的方法是线程安全吗?

是线程安全

因为每次执行WithXXX方法,都会新建一个context对象,并且把父对象进行绑定。

见demo

func main() {
	ctx := context.WithValue(context.Background(), "xiaofu", "test")
	go func() {
		_ = context.WithValue(ctx, "xiaofu", "test1")
	}()
	go func() {
		_ = context.WithValue(ctx, "xiaofu", "test2")
	}()

	fmt.Println(ctx.Value("xiaofu"))
	fmt.Println(ctx.Value("xiaofu"))
	time.Sleep(3 * time.Second)
}
//输出
//test
//test
hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » Context包源码解析(附面经)