Go语言context包源码剖析

news2024/11/26 0:41:47

context包的作用

context包是在go1.7版本中引入到标准库中的

context可以用来在goroutine之间传递上下文信息,相同的context可以传递给运行在不同goroutine中的函数,上下文对于多个goroutine同时使用是安全的,context包定义了上下文类型,可以使用backgroundTODO创建一个上下文,在函数调用链之间传播context,也可以使用WithDeadlineWithTimeoutWithCancel WithValue创建的修改副本替换它,听起来有点绕,其实总结起就是一句话:context的作用就是在不同的goroutine之间同步请求特定的数据、取消信号以及处理请求的截止日期。

context包概述

创建context

context包主要提供了两种方式创建context:

  • context.Backgroud()
  • context.TODO()

这两种方式其实并没有什么差别,只是互为别名,官方给的定义是:

  • context.Background是上下文的默认值,所有其他的上下文都应该从它衍生(Derived)出来。
  • context.TODO应该只在不确定应该使用哪种上下文时使用;

在大多数情况下,我们都使用context.Backgroud()作为起始的上下文传递
上面的两种方式是创建根context,不具备任何功能,具体实践还是要依靠context包提供的With系列函数来进行派生:

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

context包源码剖析

Context抽象接口

type Context interface {
	Deadline() (deadline time.Time, ok bool)

	Done() <-chan struct{}
	Err() error
	Value(key any) any
}
  • deadline:

Deadline 返回代表此上下文完成的工作应该被取消的时间。 当没有设置截止日期时,截止日期返回 ok==false。 对 Deadline 的连续调用返回相同的结果。

  • Done:

Done 返回一个通道,当应该取消代表此上下文完成的工作时,该通道将关闭。 如果此上下文永远无法取消,则 Done 可能会返回 nil。 对 Done 的连续调用返回相同的值。

  • WithCancel 安排在调用 cancel 时关闭 Done;
  • WithDeadline 安排在截止日期到期时关闭 Done;
  • WithTimeout 安排在超时结束时关闭 Done。

提供 Done 以供在 select 语句中使用:
Stream 使用 DoSomething 生成值并将它们发送到 out,直到 DoSomething 返回错误或 ctx.Done 关闭。

  func Stream(ctx context.Context, out chan<- Value) error {
  for {
  	v, err := DoSomething(ctx)
  	if err != nil {
  		return err
  	}
  	select {
  	case <-ctx.Done():
  		return ctx.Err()
  	case out <- v:
  	}
  }
}
  • Err

如果 Done 尚未关闭,Err 返回 nil。
如果 Done 关闭,Err 返回一个非零错误来解释原因:
如果上下文被取消,则为取消; 如果上下文的最后期限已过,则为 DeadlineExceded。
在 Err 返回非零错误后,对 Err 的连续调用返回相同的错误。

这个接口主要是被是被三个类继承实现的,分别是emptyCtxValueCtxcancelCtx,采用匿名接口的写法,这样可以对任意实现了该接口的类型进行重写。

emptyCtx

emptyCtx是一个空Context,主要充当根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 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"
}

里面的各种接口,都是空实现
context.Backgroud()context.TODO(),就是两个emotyCtx

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

func Background() Context {
	return background
}

func TODO() Context {
	return todo
}

backgroundtodo是两个单例对象,实现方法是一模一样的

valueCtx

valueCtx目的就是为Context携带数据,他会继承父Context

type valueCtx struct {
	Context
	key, val any
}

valueCtx类的创建,下面带代码的实现看起来十分简单,就是返回一个valueCtx实例

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

该类实现了String方法输出context和携带的键值对信息

func stringify(v any) string {
	switch s := v.(type) {
	case stringer:
		return s.String()
	case string:
		return s
	}
	return "<not Stringer>"
}

func (c *valueCtx) String() string {
	return contextName(c.Context) + ".WithValue(type " +
		reflectlite.TypeOf(c.key).String() +
		", val " + stringify(c.val) + ")"
}

该类还提供获取value的Value方法

func (c *valueCtx) Value(key any) any {
	if c.key == key {
		return c.val
	}
	return value(c.Context, 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 == &cancelCtxKey {
				return c
			}
			c = ctx.Context
		case *timerCtx:
			if key == &cancelCtxKey {
				return &ctx.cancelCtx
			}
			c = ctx.Context
		case *emptyCtx:
			return nil
		default:
			return c.Value(key)
		}
	}
}

该方法的实现就是从树的最底层向上找,直到找到或者到达根Context为止,context树形结构如下
在这里插入图片描述
以ctx1.1为例,现在ctex1.1找,找不到,会往ctx1.0找,再找不到就会到达根context返回err

cancelCtx

cnacelCtx也会继承父context

type cancelCtx struct {
	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                 // set to non-nil by the first cancel call
}

  • mu:保护下面的成员
  • done:用来通知context的取消信号,采用的原子操作
  • children:存放当前接口的字节点,当根节点发生取消时,所有子节点也需要取消
  • err:用来存放取消context时的信息
  • 通过newCancelCtx方法创建cancelCtx实例对象
func newCancelCtx(parent Context) cancelCtx {
	return cancelCtx{Context: parent}
}

该类也实现了Valie方法,与上面类似具体细节不过多赘述

实现了done方法,返回一个单向管道,当应该取消代表此上下文完成的工作时,该通道将关闭。 如果此上下文永远无法取消,则 Done 可能会返回 nil。 对 Done 的连续调用返回相同的值。

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

propagateCancel方法用来构建父子节点之间的关系

func propagateCancel(parent Context, child canceler) {
   //如果返回的是nil说明父节点不会被取消,就不用管,直接返回就可以
	done := parent.Done()
	if done == nil {
		return // parent is never canceled
	}

	//判断要构建关联的节点是否被取消,被取消也就不需要构建关联了,该节点直接取消
	//节点可能是timeCtx和cancelCtx
	select {
	case <-done:
		// parent is already canceled
		child.cancel(false, parent.Err())
		return
	default:
	}
  //这里的目的是去寻找可以构建关联的的context,可能是父节点,祖父节点,曾祖父节点.....
	if p, ok := parentCancelCtx(parent); ok {
		p.mu.Lock()
		if p.err != nil {
			// 构建关联的Context已经被取消,所以不需要再构建关联,子节点直接取消
			child.cancel(false, p.err)
		} else {
			//初始化children节点的childrenMap
			if p.children == nil {
				p.children = make(map[canceler]struct{})
			}
			//将该节点添加到父节点children当中
			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():
			}
		}()
	}
}

cancel方法来取消子节点

// 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
	//关闭chan,通知其他协程
	d, _ := c.done.Load().(chan struct{})
	if d == nil {
		c.done.Store(closedchan)
	} else {
		close(d)
	}
	//遍历每一个children,取消所有孩子节点
	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()

	//如果为true,则从父节点中将该节点移除
	if removeFromParent {
		removeChild(c.Context, c)
	}
}

timerCtx

timerCtx基于 cancelCtx,继承了cancelCtx只是多了一个 time.Timer和一个 deadline。Timer 会在 deadline到来时,自动取消 context。

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

	deadline time.Time
}

先看WithTimeout方法,它内部就是调用的WithDeadline方法:

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

下面看看WithDeadline

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)
	}
	//创建timerCtx实例
	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) }
}

timerCtx实现的cancel方法,内部也是调用了cancelCtxcancel方法取消:

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

以上就是Context包的大致内容

context的优缺点

缺点 :

  • 让代码变得十分丑陋,可读性变差,因为我们不得不将context作为一个参数,进行传递
  • context取消和自动取消的错误返回不够友好,无法自定义错误,出现难以排查的问题时不好排查。
  • 衍生的context就像一个个链表节点,当节点过多时,会影响代码效率

优点:

  • 可以更好的,更方便的管理协程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/143740.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

两数之和【每日一题】

⭐前言⭐ ※※※大家好&#xff01;我是同学〖森〗&#xff0c;一名计算机爱好者&#xff0c;今天让我们进入刷题模式。若有错误&#xff0c;请多多指教。更多有趣的代码请移步Gitee &#x1f44d; 点赞 ⭐ 收藏 &#x1f4dd;留言 都是我创作的最大的动力&#xff01; 合抱之木…

MySQL数据库自动补全工具

MySQL数据库操作起来命令较多&#xff0c;默认无法使用tab键进行补全&#xff0c;所以可以安装一些实用的工具进行日常的操作 方式一&#xff1a;临时使用自动补全功能 mysql -u root -p --auto-rehash 方式二&#xff1a;永久使用自动补全功能 vi /etc/my.cnf [mysql] a…

Qt扫盲-QSqlDatabase理论总结

QSqlDatabase理论总结一、概述二、使用1. 连接数据库2. 数据库驱动3. 自定义数据驱动使用4. 注意事项三、常用的功能一、概述 QSqlDatabase类提供了通过连接访问数据库的接口。QSqlDatabase的一个实例表示一个数据库连接对象。该连接是通过Qt支持的数据库驱动程序来对数据库的…

再学C语言26:分支和跳转——goto语句

goto语句包括两个部分&#xff1a;goto和一个标签名称 goto point1; 为使goto语句工作&#xff0c;程序中必须包含由point1标签定位的其他语句 point1: printf("hello world! \n"); // 标签 冒号 一条语句 示例代码&#xff1a; /* test of goto */ #include &…

高一数学试题-2022年秋期末试卷

一、选择题 已知集合A{x∈N∣−2<x<52}A \{x \in \mathbf{N}| -2 < x < \frac{5}{2}\}A{x∈N∣−2<x<25​}&#xff0c;B{−2,−1,0,1,2,4}B \{-2, -1, 0, 1, 2, 4\}B{−2,−1,0,1,2,4}&#xff0c;则A∩BA \cap BA∩B A. {−1,0,1,2}\{-1, 0, 1, 2\}{−…

什么是AI预训练模型?

什么是预训练模型? 预训练 AI 模型是一种深度学习模型&#xff0c;在大型数据集上进行训练以完成特定任务&#xff0c;它可以按原样使用或定制以满足多个行业的应用需求。 想象一下&#xff0c;试图教一个蹒跚学步的孩子什么是独角兽。 一个好的起点可能是展示该生物的子图像…

[NOIP2017 提高组] 奶酪(C++,并查集)

题目背景 NOIP2017 提高组 D2T1 题目描述 现有一块大奶酪&#xff0c;它的高度为 hhh&#xff0c;它的长度和宽度我们可以认为是无限大的&#xff0c;奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系&#xff0c;在坐标系中&#xff0c;奶酪的下表面…

用纯C实现单链表

前言 什么是单链表&#xff1f;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。链表的创建 需要创建一个小项目工程 创建三个文件 ⭐SListNode.h放单链表的头文件&#xff0c;函数声明 ⭐SListNode.c放单…

springcloud之Gateway服务网关

1.Gateway服务网关 Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 Spring 5.0&#xff0c;Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关&#xff0c;它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式…

游戏行业(北区)客户沙龙丨阿里云用户组北京站

2022 年 8 月 30 日&#xff0c;阿里云在北京举办了第 9 场阿里云用户组&#xff08;AUG&#xff09;活动&#xff0c;活动主题为“游戏行业&#xff08;北区&#xff09;客户沙龙”吸引了众多技术从业者及企业管理者来到现场交流分享。 通过本次活动&#xff0c;参会企业在不同…

Java探针技术详解

简介 在JVM中运行中&#xff0c;类是通过classLoader加载.class文件进行生成的。在类加载器加载.class文件生成对应的类对象之前时&#xff0c;我们可以通过修改.class文件内容&#xff08;就是字节码修改技术&#xff09;&#xff0c;达到修改类的目的。JDK提供了对字节码进行…

rhel配置zfs

rhel配置zfs环境声明ZFS简介ZFS块指针的结构ZFS的存储池布局rhel8/rhel9安装zfs始终在启动时加载OpenZFS模块开机自启zfs服务zfs基本操作查看磁盘创建pool和ZFS文件系统创建一个存储池设置zfs挂载点为多个用户创建起始目录向池中添加更多空间修改文件系统名称删除文件系统修改存…

数据库的存储引擎和事务

Mysql默认使用INNODB存储引擎 数据库中的各表均被&#xff08;在创建表时&#xff09;指定的存储引擎来处理。 服务器可用的引擎依赖于以下因素&#xff1a;MySql的版本、服务器在开发时如何被配置、启动选项 为了解当前服务器中有哪些存储引擎可用&#xff0c;可使用show e…

2022 年 项目中常用的工具函数

目录1. 数字千分位化&#xff0c;支持保留小数点2. 前端生成 uuid3. 传入日期换算出是周几4. 通过计算 text-indent 偏移的负值&#xff0c;实现文字居右隐藏, 右侧对齐 ...text5. 时间格式化函数6. 防抖和节流7. 前端文件流下载8. 后端返回 无格式化时间处理为格式化 20220101…

回收租赁商城系统功能拆解03讲-商品分类

回收租赁系统适用于物品回收、物品租赁、二手买卖交易等三大场景。 可以快速帮助企业搭建类似闲鱼回收/爱回收/爱租机/人人租等回收租赁商城。 回收租赁系统支持智能评估回收价格&#xff0c;后台调整最终回收价&#xff0c;用户同意回收后系统即刻放款&#xff0c;用户微信零…

【C语言进阶】指针进阶-回调函数

作者:匿名者Unit 目录一.函数指针数组1.定义2.转移表二.回调函数1.定义2.qsort的使用3.冒泡模拟实现qsort一.函数指针数组 1.定义 在之前我们已经了解过了函数指针: int(*p)(int,int)&add;我们还可以将函数的地址存放在数组&#xff0c;也就是函数指针数组 int (*p[10…

Qt音视频开发10-ffmpeg内核硬解码

一、前言 为了极大的降低CPU的占用&#xff0c;实现硬解码&#xff08;也叫硬件加速&#xff09;非常有必要&#xff0c;一个视频文件或者一路视频流还好&#xff0c;如果增加到64路视频流呢&#xff0c;如果是4K、8K这种高分辨率的视频呢&#xff0c;必须安装上硬解码才是上上…

初始Spring

初始Spring SSM框架的老大是&#xff1a;Spring大管家&#xff0c;无处不在 Spring是应用了很多优秀的设计模式,对于项目的实现,提供了优秀的解决方案;Spring是一个轻量级(低侵入) 框架.类与类之间的解耦合 IOC控制反转 实现大管家 AOP 增强&#xff0c;面向切面编程&…

QT—QPalette调色板类

Qt提供的调色板类QPalette专门用于管理部件的外观显示&#xff0c;相当于部件或对话框的调色板&#xff0c;管理他们所有的颜色信息。每个部件都包含一个QPalette对象&#xff0c;在显示时&#xff0c;按照它的QPalette对象中对各部分各状态下的颜色的描述进行绘制。示例点击左…

C语言-数据的存储-浮点数的存储(8.2)​​​​​​​

目录​​​​​​​ 思维导图&#xff1a; 浮点型在内存中的存储 1.1一个经典的例子 1.2 浮点数存储规则 1.3实践举例 写在最后&#xff1a; 思维导图&#xff1a; 浮点型在内存中的存储 1.1一个经典的例子 #include <stdio.h>int main() {int n 9;//以整形的形式…