我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Go中Context使用源码解析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Go中Context使用源码解析

前言

本篇内容的主题是Go中Context,想必已学习Go语言的大家在熟悉不过了。工作中我们也常会用到,但有时很少去注意它。

本打算将相关知识点归总一下,发现其源码不多,就打算对其源码进行分析一下。

context包是在go1.17是引入到标准库中,且标准库中大部分接口都将context.Context作为第一个参数。

context中文译为“上下文”,实际代表的是goroutine的上下文。且多用于超时控制和多个goroutine间的数据传递。

本篇文章将带领大家深入了解其内部的工作原理。

1、Context定义

Context 接口定义如下

type Context interface {
  // Deadline returns the time when this Context will be canceled, if any.
	Deadline() (deadline time.Time, ok bool)
  // Done returns a channel that is closed when this Context is canceled
  // or times out.
	Done() <-chan struct{}
  // Err indicates why this context was canceled, after the Done channel
  // is closed.
	Err() error
  // Value returns the value associated with key or nil if none.
	Value(key any) any
}

Deadline(): 返回的第一个值是 截止时间,到了这个时间点,Context 会自动触发 Cancel 动作。返回的第二个值是 一个布尔值,true 表示设置了截止时间,false 表示没有设置截止时间,如果没有设置截止时间,就要手动调用 cancel 函数取消 Context。

Done(): 返回一个只读的通道(只有在被cancel后才会返回),类型为 struct{}。当这个通道可读时,意味着parent context已经发起了取消请求,根据这个信号,开发者就可以做一些清理动作,退出goroutine。这里就简称信号通道吧!

Err():返回Context 被取消的原因

Value: 从Context中获取与Key关联的值,如果没有就返回nil

2、Context的派生

2.1、创建Context对象

context包提供了四种方法来创建context对象,具体方法如下:

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

由以上方法可知:新的context对象都是基于父context对象衍生的.

  • WithCancel:创建可以取消的Context
  • WithDeadline: 创建带有截止时间的Context
  • WithTimeout:创建带有超时时间的Context,底层调用的是WithDeadline方法
  • WithValue:创建可以携带KV型数据的Context

简单的树状关系如下(实际可衍生很多中):

2.2、parent Context

context包默认提供了两个根context 对象backgroundtodo;看实现两者都是由emptyCtx创建的,两者的区别主要在语义上,

  • context.Background 是上下文的默认值,所有其他的上下文都应该从它衍生出来;
  • context.TODO 应该仅在不确定应该使用哪种上下文时使用
var (
	background = new(emptyCtx)
	todo       = new(emptyCtx)
)
// Background 创建background context
func Background() Context {
	return background
}
// TODO 创建todo context
func TODO() Context {
	return todo
}

3、context 接口四种实现

具体结构如下,我们大致看下相关结构体中包含的字段,具体字段的含义及作用将在下面分析中会提及。

  • emptyCtx
type emptyCtx int // 空context
  • cancelCtx
type cancelCtx struct {
	Context // 父context
	mu       sync.Mutex            // protects following fields
	done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call
	children map[canceler]struct{} // set to nil by the first cancel call
	err      error                 // cancel的原因
}
  • timerCtx
type timerCtx struct {
	cancelCtx //父context
	timer *time.Timer // 定时器
	deadline time.Time // 截止时间
}
  • valueCtx
type valueCtx struct {
	Context // 父context
  key, val any // kv键值对
}

4、 emptyCtx 源码分析

emptyCtx实现非常简单,具体代码如下,我们简单看看就可以了

// An emptyCtx is never canceled, has no values, and has no deadline. It is not
// struct{}, since vars of this type must have distinct addresses.
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 any) any {
	return nil
}
func (e *emptyCtx) String() string {
	switch e {
	case background:
		return "context.Background"
	case todo:
		return "context.TODO"
	}
	return "unknown empty Context"
}

5、 cancelCtx 源码分析

cancelCtx 的实现相对复杂点,比如下面要介绍的timeCtx 底层也依赖它,所以弄懂cancelCtx的工作原理就能很好的理解context.

cancelCtx 不仅实现了Context接口也实现了canceler接口

5.1、对象创建withCancel()

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	if parent == nil { // 参数校验
		panic("cannot create context from nil parent")
	}
  // cancelCtx 初始化
	c := newCancelCtx(parent)
	propagateCancel(parent, &c) // cancelCtx 父子关系维护及传播取消信号
	return &c, func() { c.cancel(true, Canceled) } // 返回cancelCtx对象及cannel方法
}
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

用户调用WithCancel方法,传入一个父 Context(这通常是一个 background,作为根节点),返回新建的 context,并通过闭包的形式返回了一个 cancel 方法。如果想要取消context时需手动调用cancel方法。

5.1.1、newCancelCtx

cancelCtx对象初始化, 其结构如下:

type cancelCtx struct {
  // 父 context
	Context //  parent context
	// 锁 并发场景下保护cancelCtx结构中字段属性的设置
	mu       sync.Mutex            // protects following fields 
  // done里存储的是信号通道,其创建方式采用的是懒加载的方式
	done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call 
  // 记录与父子cancelCtx对象,
	children map[canceler]struct{} // set to nil by the first cancel call
  // 记录ctx被取消的原因
	err      error                 // set to non-nil by the first cancel call
}

5.1.2、propagateCancel

propagateCancel

// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
	done := parent.Done() // 获取parent ctx的信号通道 done
	if done == nil {  // nil 代表 parent ctx 不是canelctx 类型,不会被取消,直接返回
		return // parent is never canceled
	}
	select { // parent ctx 是cancelCtx类型,判断其是否被取消
	case <-done:
		// parent is already canceled
		child.cancel(false, parent.Err())
		return
	default:
	}
  //parentCancelCtx往树的根节点方向找到最近的context是cancelCtx类型的
	if p, ok := parentCancelCtx(parent); ok { // 查询到
		p.mu.Lock() // 加锁
		if p.err != nil { // 祖父 ctx 已经被取消了,则 子cancelCtx 也需要调用cancel 方法来取消
			// parent has already been canceled
			child.cancel(false, p.err)
		} else { // 使用map结构来维护 将child加入到祖父context中
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			p.children[child] = struct{}{}
		}
		p.mu.Unlock()// 解锁
	} else { // 开启协程监听 parent Ctx的取消信号 来通知child ctx 取消
		atomic.AddInt32(&goroutines, +1)
		go func() {
			select {
			case <-parent.Done():
				child.cancel(false, parent.Err())
			case <-child.Done():
			}
		}()
	}
}
// parentCancelCtx returns the underlying *cancelCtx for parent.
// It does this by looking up parent.Value(&cancelCtxKey) to find
// the innermost enclosing *cancelCtx and then checking whether
// parent.Done() matches that *cancelCtx. (If not, the *cancelCtx
// has been wrapped in a custom implementation providing a
// different done channel, in which case we should not bypass it.)
// parentCancelCtx往树的根节点方向找到最近的context是cancelCtx类型的
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
	done := parent.Done()
  // closedchan 代表此时cancelCtx 已取消, nil 代表 ctx不是cancelCtx 类型的且不会被取消
	if done == closedchan || done == nil { 
		return nil, false
	}
  // 向上遍历查询canelCtx 类型的ctx
	p, ok := parent.Value(&cancelCtxKey).(*cancelCtx)
	if !ok { // 没有
		return nil, false
	}
  // 存在判断信号通道是不是相同
	pdone, _ := p.done.Load().(chan struct{})
	if pdone != done {
		return nil, false
	}
	return p, true
}

5.2 canceler

cancelCtx也实现了canceler接口,实现可以 取消上下文的功能。

canceler接口定义如下:

// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
	cancel(removeFromParent bool, err error) // 取消
	Done() <-chan struct{} // 只读通道,简称取消信号通道
}

cancelCtx 接口实现如下:

整体逻辑不复杂,逻辑简化如下:

  • 当前 cancelCtx 取消 且 与之其关联的子 cancelCtx 也取消
  • 根据removeFromParent标识来判断是否将子 cancelCtx 移除

注意

由于信号通道的初始化采用的懒加载方式,所以有未初始化的情况;

已初始化的:调用close 函数关闭channel

未初始化的:用 closedchan 初始化,其closedchan是已经关闭的channel。

// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's 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
	d, _ := c.done.Load().(chan struct{})
	if d == nil {
		c.done.Store(closedchan)
	} else {
		close(d)
	}
	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)
	}
}
// removeChild removes a context from its parent.
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()
}

closedchan

可重用的关闭通道,该channel通道默认已关闭

// closedchan is a reusable closed channel.
var closedchan = make(chan struct{})
func init() {
	close(closedchan) // 调用close 方法关闭
}

6、timerCtx 源码分析

cancelCtx源码已经分析完毕,那timerCtx理解起来就很容易。

关注点timerCtx是如何取消上下文的,以及取消上下文的方式

6.1、对象创建 WithDeadline和WithTimeout

WithTimeout 底层调用是WithDeadline 方法 ,截止时间是 now+timeout;

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
	return WithDeadline(parent, time.Now().Add(timeout))
}

WithDeadline 整体逻辑并不复杂,从源码中可分析出timerCtx取消上下文 采用两种方式 自动手动;其中自动方式采用定时器去处理,到达触发时刻,自动调用cancel方法。

deadline: 截止时间

timer *time.Timer : 定时器

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

6.2 timerCtx的cancel

  • 调用cancelCtx的cancel 方法
  • 根据removeFromParent标识,为true 调用removeChild 方法 从它的父cancelCtx的children中移除
  • 关闭定时器 ,防止内存泄漏(着重点)
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()
}

7、valueCtx 源码分析

7.1、对象创建WithValue

valueCtx 结构体中有keyval两个字段,WithValue 方法也是将数据存放在该字段上

func WithValue(parent Context, key, val any) 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}
}
// 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 any
}

7.2、获取value值

func (c *valueCtx) Value(key any) any {
	if c.key == key { // 判断当前valuectx对象中的key是否匹配
		return c.val
	}
	return value(c.Context, key)
}
// value() 向根部方向遍历,直到找到与key对应的值
func value(c Context, key any) any {
	for {
		switch ctx := c.(type) {
		case *valueCtx:
			if key == ctx.key {
				return ctx.val
			}
			c = ctx.Context
		case *cancelCtx:
			if key == &amp;cancelCtxKey { // 获取cancelCtx对象
				return c
			}
			c = ctx.Context
		case *timerCtx:
			if key == &amp;cancelCtxKey {
				return &amp;ctx.cancelCtx
			}
			c = ctx.Context
		case *emptyCtx:
			return nil
		default:
			return c.Value(key)
		}
	}
}

总结:从Context 中获取对应的值需要通过遍历的方式来获取,这里告诫我们嵌套太多的context反而对性能会有影响

8、规范&注意事项

  • 不要把context存在一个结构体当中,显式地传入函数。context变量需要作为第一个参数使用,一般命名为ctx;
  • 即使方法允许,也不要传入一个nil的Context,如果你不确定你要用什么Context的时候传一个context.TODO
  • 使用context的Value相关方法只应该用于在程序和接口中传递的和请求相关的元数据,不要用它来传递一些可选的参数
  • 同样的Context可以用来传递到不同的goroutine中,Context在多个goroutine中是安全的

以上就是Go中Context使用源码解析的详细内容,更多关于Go Context源码解析的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Go中Context使用源码解析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Go中Context使用源码解析

这篇文章主要为大家介绍了Go中Context使用源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-16

Android context源码详解及深入分析

Android context详解 前言: Context都没弄明白,还怎么做Android开发? Activity mActivity =new Activity()作为Android开发者,不知道你有没有思考过这个问题,Activity
2022-06-06

Go中的Context怎么使用

这篇“Go中的Context怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Go中的Context怎么使用”文章吧。1
2023-07-06

react中context传值和生命周期源码分析

本篇内容主要讲解“react中context传值和生命周期源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“react中context传值和生命周期源码分析”吧!假设:项目中存在复杂组件树:
2023-07-05

Golang中Slice的分析与使用源码解析

这篇文章主要介绍了Golang中Slice的分析与使用(含源码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2023-03-09

Golang中Slice使用源码分析

本文小编为大家详细介绍“Golang中Slice使用源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“Golang中Slice使用源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1、slice结构体首
2023-07-05

详解go中panic源码解读

panic源码解读 前言 本文是在go version go1.13.15 darwin/amd64上进行的 panic的作用panic能够改变程序的控制流,调用panic后会立刻停止执行当前函数的剩余代码,并在当前Goroutine中递归
2022-06-07

Android AsyncTask使用以及源码解析

综述在Android中,我们需要进行一些耗时的操作,会将这个操作放在子线程中进行。在子线程操作完成以后我们可以通过Handler进行发送消息,通知UI进行一些更新操作(具体使用及其原理可以查看Android的消息机制——Handler的工作
2022-06-06

ResponseBodyAdvice的使用原理源码解析

这篇文章主要为大家介绍了ResponseBodyAdvice的使用原理源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-13

Go并发之RWMutex的源码解析详解

RWMutex是一个支持并行读串行写的读写锁。RWMutex具有写操作优先的特点,写操作发生时,仅允许正在执行的读操作执行,后续的读操作都会被阻塞。本文就来从源码解析一下RWMutex的使用
2023-03-15

源码剖析Android中Okio的使用

这篇文章主要将从源码出发,带大家剖析一下Android中Okio的具体使用,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以了解一下
2023-02-17

vue使用源码分析

本篇内容主要讲解“vue使用源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“vue使用源码分析”吧!生命周期1.0版本1.哪些生命周期接口initCreatedbeforeCompileC
2023-07-04

Android StateMachine使用举例及源码解析

Android frameworks源码StateMachine使用举例及源码解析 工作中有一同事说到Android状态机StateMachine。作为一名Android资深工程师,我居然没有听说过StateMachine,因此抓紧时间学习
2022-06-06

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录