设为首页 加入收藏

TOP

Go语言 context包源码学习(四)
2023-07-23 13:25:51 】 浏览:115
Tags:语言 context 包源码
= doSomething(ctx) fmt.Println(res, err) // nil , context canceled }

接下来就让我们来研究下,cancelCtx 是如何实现取消的吧

类型定义

  • canceler 接口包含 cancel() 和 Done() 方法,*cancelCtx 和 *timerCtx 均实现了这个接口。
  • closedchan 是一个被关闭的channel,可以用于后面 Done() 返回
  • canceled 是一个 err,用于 Context 被取消的原因
type canceler interface {
	cancel(removeFromParent bool, err error)
	Done() <-chan struct{}
}

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

func init() {
	close(closedchan)
}

var Canceled = errors.New("context canceled")

CancelFunc 是一个函数类型定义,是一个取消函数,有如下规范:

  • CancelFunc 告诉一个任务停止工作
  • CancelFunc 不会等待任务结束
  • CancelFunc 支持并发调用
  • 第一次调用后,后续的调用不会产生任何效果
type CancelFunc func()

&cancelCtxKey 是一个固定的key,用来返回 cancelCtx 自身

var cancelCtxKey int

cancelCtx

cancelCtx 是可以被取消的,它嵌套了 Context 接口,实现了 canceler 接口。cancelCtx 使用 children 字段保存同样实现 canceler 接口的子节点,当 cancelCtx 被取消时,所有的子节点也会取消。

type cancelCtx struct {
	Context

	mu       sync.Mutex            // 保护如下字段,保证线程安全
	done     atomic.Value          // 保存 channel,懒加载,调用 cancel 方法时会关闭这个 channel
	children map[canceler]struct{} // 保存子节点,第一次调用 cancel 方法时会置为 nil
	err      error                 // 保存为什么被取消,默认为nil,第一次调用 cancel 会赋值
}

*cancelCtx 的 Value() 方法 和 *valueCtx 的 Value() 方法类似,只不过加了个固定的key: &cancelCtxKey。当key 为 &cancelCtxKey 时返回自身

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

*cancelCtx 的 done 字段是懒加载的,只有在调用 Done() 方法 或者 cancel() 时才会赋值。

func (c *cancelCtx) Done() <-chan struct{} {
	d := c.done.Load()
  
  // 如果已经有值了,直接返回
	if d != nil {
		return d.(chan struct{})
	}
  
  // 没有值,加锁赋值
	c.mu.Lock()
	defer c.mu.Unlock()
	d = c.done.Load()
	if d == nil {
		d = make(chan struct{})
		c.done.Store(d)
	}
	return d.(chan struct{})
}

Err 方法返回 cancelCtx 的 err 字段

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

WithCancel

那么我们如何新建一个 cancelCtx呢?context 包提供了 WithCancel() 方法,让我们基于一个 Context 来创建一个 cancelCtx。WithCancel() 方法返回两个字段,一个是基于传入的 Context 生成的 cancelCtx,另一个是 CancelFunc。

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

WithCancel 调用了两个外部方法:newCancelCtx 、propagateCancel。newCancelCtx 比较简单,根据传入的 context,返回了一个 cancelCtx 结构体。

func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

propagateCancel 从名字可以看出,就是将 cancel 传播。如果父Context支持取消,那么我们需要建立一个通知机制,这样父节点取消的时候,通知子节点也取消,层层传播。

在 propagateCancel 中,如果 父Context 是 cancelCtx 类型且未取消,会将 子Context 挂在它下面,形成一个树结构;其余情况都不会挂载。

func propagateCancel(parent Context, child canceler) {
  
  // 如果 parent 不支持取消,那么就不支持取消传播,直接返回
	done := parent.Done()
	if done == nil {
		return 
	}

  // 到这里说明 done 不为 nil,parent 支持取消
  
	select {
	case <-done:
		// 如果 parent 此时已经取消了,那么直接告诉子节点也取消
		child.cancel(false, parent.Err())
		return
	default:
	}

  // 到这里说明此时 parent 还未取消
  
  // 如果 parent 是未取消的 cancelCtx 
	if p, ok := parentCancelCtx(parent); ok {
    
    // 加锁,防止并发更新
		p.mu.Lock()
    
    // 再次判断,因为有可能上一个获得锁的进行了取消操作。
    // 如果 parent 已经取消了,那么子节点也直接取消
		if p.err != nil {
			child.cancel(false, p.err)
		} else {
      // 把子Context 挂到父节点 parent cancelCtx 的 children字段下
      // 之后 parent cancelCtx 取消时,能通知到所有的 子Context 
			if p.children ==
首页 上一页 1 2 3 4 5 6 下一页 尾页 4/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇为什么说 Go 语言字符串是不可变.. 下一篇[grpc快速入门] 一 grpc生成与调用

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目