读完Golang源码之Context

本文详细介绍了Go1.7标准库中的Context概念,探讨其在goroutine间传递取消信号、超时时间、截止时间及元数据的应用。解析Context的实现原理,包括emptyCtx、cancelCtx、timerCtx和valueCtx等关键结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于源码Golang1.14.2

介绍

Go 1.7 标准库引入 context,中文译作 “上下文”,准确说它是 goroutine 的上下文,包含 goroutine 的运行状态、环境、现场等信息。

context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等。

context 用来解决 goroutine 之间退出通知、元数据传递的功能。

实现

包整体介绍
名称类型作用
Context接口定义了 Context 接口的四个方法
emptyCtx结构体实现了 Context 接口,它其实是个空的 context
CancelFunc函数取消函数
canceler接口context 取消接口,定义了两个方法
cancelCtx结构体可以被取消
timerCtx结构体超时会被取消
valueCtx结构体可以存储 k-v 对
Background函数返回一个空的 context,常作为根 context
TODO函数返回一个空的 context,常用于重构时期,没有合适的 context 可用
WithCancel函数基于父 context,生成一个可以取消的 context
newCancelCtx函数创建一个可取消的 context
propagateCancel函数向下传递 context 节点间的取消关系
parentCancelCtx函数找到第一个可取消的父节点
removeChild函数去掉父节点的孩子节点
init函数包初始化
WithDeadline函数创建一个有 deadline 的 context
WithTimeout函数创建一个有 timeout 的 context
WithValue函数创建一个存储 k-v 对的 context
Context接口
type Context interface {
    // 当 context 被取消或者到了 deadline,返回一个被关闭的 channel
    Done() <-chan struct{}

    // 在 channel Done 关闭后,返回 context 取消原因
    Err() error

    // 返回 context 是否会被取消以及自动取消时间(即 deadline)
    Deadline() (deadline time.Time, ok bool)

    // 获取 key 对应的 value
    Value(key interface{}) interface{}
}
emptyCtx

我们常使用的Background,TODO都是emptyCtx的实体:

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

func Background() Context {
	return background
}

func TODO() Context {
	return todo
}

其实这两个值没有任何区别,不过官方建议Background()用于根目录也就是main.go,TODO用来作为填充值(任何你不知道填什么context的地方)
以下是emptyCtx对于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"
}
canceler 接口
type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}

由于Done接口是重复的,所以只要实现了cancel接口的ctx就实现了这个接口,其中cancelCtx和timerCtx就实现了

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

func (c *cancelCtx) Value(key interface{}) interface{} {
	if key == &cancelCtxKey {
		return c
	}
	return c.Context.Value(key)
}

func (c *cancelCtx) Done() <-chan struct{} {
	c.mu.Lock()
	if c.done == nil {
		c.done = make(chan struct{})
	}
	d := c.done
	c.mu.Unlock()
	return d
}

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 = closedchan
	} else {
		close(c.done)
	}
	for child := range c.children {
		// NOTE: acquiring the child's lock while holding parent's lock.
		child.cancel(false, err)
	}
	c.children = nil
	c.mu.Unlock()

	if removeFromParent {
		removeChild(c.Context, c)
	}
}
func removeChild(parent Context, child canceler) {
	p, ok := parentCancelCtx(parent)
	if !ok {
		return
	}
	p.mu.Lock()
	if p.children != nil {
		delete(p.children, child)
	}
	p.mu.Unlock()
}

cancelCtx通过实现Done和cancel以实现了canceler接口,说明cancelCtx是一个可以手动取消的上下文。
cacel:取消自身上下文,并把自身从上个context中移除。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := newCancelCtx(parent)
	propagateCancel(parent, &c)
	return &c, func() { c.cancel(true, Canceled) }
}

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

func propagateCancel(parent Context, child canceler) {
	done := parent.Done()
	if done == nil {
		return // parent is never canceled
	}

	select {
	case <-done:
		// parent is already canceled
		child.cancel(false, parent.Err())
		return
	default:
	}

	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
		if p.err != nil {
			// parent has already been canceled
			child.cancel(false, p.err)
		} else {
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
	} else {
		atomic.AddInt32(&goroutines, +1)
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

func propagateCancel(parent Context, child canceler) {
	done := parent.Done()
	if done == nil {
		return // parent is never canceled
	}

	select {
	case <-done:
		// parent is already canceled
		child.cancel(false, parent.Err())
		return
	default:
	}

	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
		if p.err != nil {
			// parent has already been canceled
			child.cancel(false, p.err)
		} else {
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()
	} else {
		atomic.AddInt32(&goroutines, +1)
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}

parentCancelCtx:是获取父类时候是cancelCtx,如果是则返回本身,如果不是则不返回。
propagateCancel:如果父类是cancelCtx,则自己挂载上去,当父类取消时,自身也会取消。如果不是则启动一个写成来监听父类和自身的取消。

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) {
		// The current deadline is already sooner than the new one.
		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) }
}

但其实WIthTimeout也是调用的WithDeadline,所以只有timerCtx这一种结构。这个接口比cancelCtx只是多了一个定时器,多了一种取消方式,其他都没变。

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

valueCtx是用来存数据的,数据格式为key-value。但是如果有多组key-value要书写成如下方式:

	c := context.Background()
	ctx := context.WithValue(c, "k1", "v1")
	ctx = context.WithValue(ctx, "k2", "v2")

使用套娃的方式来存储,且后添加的key可能会覆盖之前的key。非常不建议用context来存储数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值