ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Context包源码解析(附面经)

2022-02-17 18:01:31  阅读:127  来源: 互联网

标签:context 附面经 Context parent cancelCtx 源码 key time


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

标签:context,附面经,Context,parent,cancelCtx,源码,key,time
来源: https://www.cnblogs.com/xiaofua/p/15905769.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有