【golang】Context超时控制与原理

Context

在Go语言圈子中流行着一句话:

Never start a goroutine without knowing how it will stop。

翻译:如果你不知道协程如何退出,就不要使用它。

在创建协程时,我们可能还会再创建一些别的子协程,那么这些协程的退出就成了问题。在Go1.7之后,Go官方引入了Context来实现协程的退出。不仅如此,Context还提供了跨协程、甚至是跨服务的退出管理。

Context本身的含义是上下文,我们可以理解为它内部携带了超时信息、退出信号,以及其他一些上下文相关的值(例如携带本次请求中上下游的唯一标识trace_id)。由于Context携带了上下文信息,父子协程之间就可以”联动“ 了。

Context标准库

在Context标准库中重要的结构 context.Context其实是一个接口,它提供了Deadline、Done、Err、Value这4种方法:

type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key interface{}) interface{}}
  • Deadline方法用于返回Context的过期时间。Deadline第一个返回值表示Context的过期时间,第二个返回值表示是否设置了过期时间,如果多次调用Deadline方法会返回相同的值。

  • Done是使用最频繁的方法,它会返回一个通道。一般的做法是调用者在select中监听该通道的信号,如果该通道关闭则表示服务超时或异常,需要执行后续退出逻辑。多次调用Done方法会返回相同的通道。

  • 通道关闭后,Err方法回返回退出的原因。

  • Value方法返回指定Key对应的value,这是Context携带的值。Key必须是可比较的,一般用法Key是一个全局变量,通过context.WithValue将key存储到Context中,并通过Context.Value方法退出。

Context是一个接口,这意味着需要有对应的具体实现。用户可以自己实现Context接口,并严格遵守Context接口。

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
}

因此,要具体使用Context,需要派生出新的Context。我们使用的最多的还是Go标准库中的实现。
前三个函数都用于派生出有退出功能的Context。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
  • WithCancel函数回返回一个子Context和cancel方法。子Context会在两种情况下触发退出:一种情况是调用者主动调用了返回的cancel方法;另一种情况是当参数中的父Context退出时,子Context将级联退出。
  • WithTimeout函数指定超时时间。当超时发生后,子Context将退出。因此,子Context的退出有三种时机,一种是父Context退出;一种是超时退出;最后一种是主动调用cancel函数退出。
  • WithDeadline和WithTimeout函数的处理方法相似,不过它们的参数指定的是最后到期的时间。
  • WithValue函数会返回带key-value的子Context。

Context实践

eg:

下面的代码中childCtx是preCtx的子Context,其设置的超时时间为300ms。但是preCtx的超时时间为100ms,因此父Context退出后,子Context会立即退出,实际的等待时间只有100ms。

func main() {ctx := context.Background()before := time.Now()preCtx, _ := context.WithTimeout(ctx, 100*time.Millisecond)go func() {childCtx, _ := context.WithTimeout(preCtx, 300*time.Millisecond)select {case <-childCtx.Done():after := time.Now()fmt.Println("child during:", after.Sub(before).Milliseconds())}}()select {case <-preCtx.Done():after := time.Now()fmt.Println("pre during:", after.Sub(before).Milliseconds())}}

这是输出如下,父Context与子Context退出的时间差接近100ms:

pre during: 104
child during: 104

当我们把preCtx的超时时间修改为500ms时:

preCtx ,_:= context.WithTimeout(ctx,500*time.Millisecond)

从新的输出中可以看出,子协程的退出不会影响父协程的退出。

child during: 304
pre during: 500

Context底层原理

Context在很大程度上利用了通道的一个特性:通道在close时,会通知所有监听它的协程。

每个派生出的子Context都会创建一个新的退出通道,这样,只要组织好Context之间的关系,就可以实现继承链上退出信号的传递。如图所示的三个协程中,关闭通道A会连带关闭调用链上的通道B,通道B会关闭通道C。
在这里插入图片描述
要使用context的退出功能,需要调用WithCancel或WithTimeout,派生出一个新的结构Context。WithCancel底层对应的结构为cancelCtx,WithTimeout底层对应的结构为timerCtx,timerCtx包装了cancelCtx,并存储了超时时间。

type cancelCtx struct {Contextmu       sync.Mutex            // protects following fieldsdone     atomic.Value          // of chan struct{}, created lazily, closed by first cancel callchildren map[canceler]struct{} // set to nil by the first cancel callerr      error                 // set to non-nil by the first cancel callcause    error                 // set to non-nil by the first cancel call
}type timerCtx struct {cancelCtxtimer *time.Timer // Under cancelCtx.mu.deadline time.Time
}

cancelCtx第一个字段保留了父Context的信息。children字段则保存了当前Context派生的子Context的信息,每个Context都会有一个单独的done通道。

而WithDeadline函数会先判断父Context设置的超时时间是否比当前Context的超时时间短,如果是,那么子协程会随着父Context的退出而退出,没有必要再设置定时器。

当我们使用了标准库中默认的Context实现时,propagateCancel函数将子Context加入父协程的children哈希表中,并开启一个定时器。当定时器到期时,会调用cancel方法关闭通道,级联关闭当前Context派生的子Context,并取消与父Context的绑定关系。这种特性就产生了调用链上连锁的退出反应。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git使用的常用指令

git作为一个版本控制工具&#xff0c;和maven并合称为实习的两大杀手工具。今天我来给大家介绍一下git的常用指令&#xff0c;帮助大家在实习和多人协同开发的时候提供一些帮助。 找到git管理的文件夹 命令1 git init 这个命令是为了初始化本地库 命令2 查看当前的git状态…

Springboot 子工程构建完后无法找到springboot依赖

问题: 构建完子工程后无法找到SpringBootTest 解决方案: 最好用这个构建 https://www.cnblogs.com/he-wen/p/16735239.html 1.先观察项目目录 是否正确 2.观察子工程目录 3.看pom.xml中是否引用springboot依赖 4.检查代码 查看父项目是否包含子模块 查看子模块的父项目是否…

AI教我学编程之C#类的基本概念(1)

前言 在AI教我学编程之C#类型 中&#xff0c;我们学习了C#类型的的基础知识&#xff0c;而类正是类型的一种. 目录 区分类和类型 什么是类&#xff1f; 对话AI 追问 实操 追踪属性的使用 AI登场 逐步推进 提出疑问 药不能停 终于实现 探索事件的使用 异步/交互操作 耗时操…

选择排序(二)——堆排序(性能)与直接选择排序

目录 一.前言 二.选择排序 2.1 堆排序 2.2选择排序 2.2.1 基本思想 2.2.2直接选择排序 三.结语 一.前言 本文给大家带来的是选择排序&#xff0c;其中选择排序中的堆排序在之前我们已经有过详解所以本次主要是对比排序性能&#xff0c;感兴趣的友友可移步观看堆排&#…

Pyro —— Creating Explosions

目录 Sourcing Adding debris to an explosion Adding sparks to an explosion Trails Trail Path Shapes Trail Source Types Understanding trails Incorporating trails into your explosion Sourcing Pyro Burst Source节点创建爆炸核心源&#xff0c;且对外观塑形…

Linux 系统之部署 h5ai 目录列表程序

一、h5ai 介绍 1.1&#xff09;h5ai 简介 h5ai 是用于 HTTP Web 服务器的现代文件索引器&#xff0c;专注于您的文件。目录以吸引人的方式显示&#xff0c;浏览它们通过不同的视图、面包屑和树概述得到增强。最初 h5ai 是 HTML5 Apache Index 的首字母缩写&#xff0c;但现在它…

midjourney充值订阅卡被拒绝了怎么办?

一、 AI绘图是什么&#xff1f; 就是AI绘画&#xff0c;顾名思义就是利用人工智能进行绘画&#xff0c;是人工智能生成内容&#xff08;AIGC&#xff09;的一个应用场景。其主要原理简单来说就是收集大量已有作品数据&#xff0c;通过算法对它们进行解析&#xff0c;最后再生成…

消息队列介绍

什么是 MQ MQ(message queue)&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&#xff0c;只不过队列中存放的内容是 message 而已&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递消息。在互联网架构中&#xff0c;MQ 是一种非常常 见的上下游“逻辑解耦…

131. 分割回文串 - 力扣(LeetCode)

问题描述 给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字符串。 输入示例 s "aab"输出示例 [["a","a","b"],["…

Spring Boot 配置双数据源

Spring Boot 配置双数据源 目录概述需求&#xff1a; 设计思路实现思路分析1.基本步骤2.实例 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for cha…

SpringBoot 统计API接口用时该使用过滤器还是拦截器?

统计请求的处理时间&#xff08;用时&#xff09;既可以使用 Servlet 过滤器&#xff08;Filter&#xff09;&#xff0c;也可以使用 Spring 拦截器&#xff08;Interceptor&#xff09;。两者都可以在请求处理前后插入自定义逻辑&#xff0c;从而实现对请求响应时间的统计。 …

一个简单的ETCD GUI工具

使用ETCD没有好用的GUI工具&#xff0c;随手用c#写了一个&#xff0c; 做得好玩的一个ETCD GUI工具&#xff0c;后面加上CLI 工具&#xff0c;类似于 redis Cli工具一样&#xff0c;简化在 Linux下面的操作&#xff0c;不知道有没有必要&#xff0c; git 地址如下&#xff0c;…

【AI】小白入门笔记

前言 2024年&#xff0c;愿新年胜旧年&#xff01;作为AI世界的小白&#xff0c;今天先来从一些概念讲起&#xff0c;希望路过的朋友们多多指教&#xff01; 正文 AI (人工智能) 提起AI, 大家可能会想起各种机器人&#xff0c;移动手机的“Siri”,"小爱同学", 是语…

翻译: Anaconda 与 miniconda的区别

Anaconda 和 miniconda 是广泛用于数据科学的软件发行版&#xff0c;用于简化包管理和部署。 1. 主要有两个区别&#xff1a; packages包数量&#xff1a; Anaconda 附带了 150 多个数据科学包&#xff0c;而 miniconda 只有少数几个。Interface接口&#xff1a;Anaconda 有…

中仕教育:研究生毕业可以考选调生吗?

选调生的报考条件之一是应届生&#xff0c;研究生毕业也属于应届生&#xff0c;所以是可以报考的。 选调生不同学历的年龄限制&#xff1a; 1.应届本科生&#xff1a;年龄在25岁以内 2.应届研究生&#xff1a;年龄在30岁以内 3.应届博士生&#xff1a;年龄在35岁以内 研究…

Elasticsearch8 集群搭建(二)配置篇:(1)节点和集群配置

安装完Elasticsearch后&#xff0c;需要对其进行配置&#xff0c;包括以下几部分&#xff1a;节点和集群配置、系统配置、安全配置。 此篇记录节点和集群配置的内容&#xff0c;后续将更新系统配置和安全配置。 节点和集群配置&#xff1a; 通过编辑/usr/local/elasticsearc…

【Oracle】收集Oracle数据库内存相关的信息

文章目录 【Oracle】收集Oracle数据库内存相关的信息收集Oracle数据库内存命令例各命令的解释输出结果例参考 【声明】文章仅供学习交流&#xff0c;观点代表个人&#xff0c;与任何公司无关。 编辑|SQL和数据库技术(ID:SQLplusDB) 【Oracle】收集Oracle数据库内存相关的信息 …

力扣刷MySQL-第三弹(详细讲解)

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;力扣刷题讲解-MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出…

Find My相机|苹果Find My技术与相机结合,智能防丢,全球定位

相机是一种利用光学成像原理形成影像并使用底片记录影像的设备&#xff0c;是用于摄影的光学器械。相机让我们能够记录下美丽的风景和珍贵的时刻。当我们到达一个迷人的地方,或者经历了一个特别难忘的时刻时,我们可以使用照相机来拍摄照片,记录下这些美好的回忆。照相机可以帮助…

[学习笔记]刘知远团队大模型技术与交叉应用L3-Transformer_and_PLMs

RNN存在信息瓶颈的问题。 注意力机制的核心就是在decoder的每一步&#xff0c;都把encoder的所有向量提供给decoder模型。 具体的例子 先获得encoder隐向量的一个注意力分数。 注意力机制的各种变体 一&#xff1a;直接点积 二&#xff1a;中间乘以一个矩阵 三&#xff1a;…