【golang】Context超时控制与原理

news2025/1/12 9:05:05

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() error
   Value(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 {
	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
	cause    error                 // set to non-nil by the first cancel call
}

type timerCtx struct {
	cancelCtx
	timer *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.coloradmin.cn/o/1401647.html

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

相关文章

【书籍】强化学习第二版(英文版电子版下载、github源码)-附copilot翻译的中英文目录...

Python代码&#xff1a;https://github.com/ShangtongZhang/reinforcement-learning-an-introduction 英文原版书籍下载&#xff1a;http://incompleteideas.net/book/the-book-2nd.html 作者&#xff1a; 理查德S萨顿是阿尔伯塔大学计算机科学教授和强化学习与人工智能 AITF 主…

1.6万字全面掌握 BERT:自然语言处理(NLP)从初学到高级的全面指南

BERT&#xff08;双向编码器表示来自Transformer的模型&#xff09;是由Google开发的一种革命性的自然语言处理&#xff08;NLP&#xff09;模型。它改变了语言理解任务的格局&#xff0c;使机器能够理解语言中的上下文和细微差异。 在本博客中&#xff0c;我们将带您从 BERT …

信息安全概述

信息安全&#xff1a;防止任何对数据进行未授权访问的措施&#xff0c;或者防止造成信息有意无意泄漏、破坏、丢失等问题的发生&#xff0c;让数据处于远离危险、免于威胁的状态或特性。 网络安全&#xff1a;计算机网络环境下的信息安全。 信息安全现状及挑战 数字化时代威…

什么是VUE 创建第一个VUE实例

一、什么是Vue 概念&#xff1a;Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套 构建用户界面 的 渐进式 框架 Vue2官网&#xff1a;Vue.js 1.什么是构建用户界面 基于数据渲染出用户可以看到的界面 2.什么是渐进式 所谓渐进式就是循序渐进&#xff0c;不一定非得把Vu…

Docker编译多平台文件、构建多平台镜像并运行

参考官方文档&#xff1a; Multi-stageMulti-platformMulti-platform images 文章目录 方法Buildx 设置使用QEMU仿真编译文件构建镜像 使用交叉编译&#xff08;cross-compilation&#xff09;编译文件构建镜像 在x86_64上运行arm64容器 方法 如果在 x86_64/amd64 的平台上&am…

Docker 操作之数据卷挂载【云原生】

文章目录 1. 镜像1.1 基本命令1.2 案例练习 2. 容器2.1 基本命令2.2 Nginx 案例2.3 Redis 案例 3. 数据卷3.1 基本命令3.2 Nginx 案例3.3 MySQL 案例 1. 镜像 镜像命令一般分两部分组成&#xff1a;repository:tag。比如 mysql:5.7&#xff0c;表示名称加版本。 在没有指定 t…

一文掌握Python所有常见的循环用法

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 循环是编程中的重要概念&#xff0c;它允许我们重复执行一组操作&#xff0c;直到满足某个条件。Python提供了多种类型的循环&#xff0c;以适应不同的需求和场景。本文将详细介绍Python中所有常见的循环用法&am…

MySQL下对[表]的操作数据类型

目录 表的操作 创建表&#xff1a; 查看表结构&#xff1a; 修改表&#xff1a; 删除表&#xff1a; 数据类型 数据类型分类&#xff1a; 数值类型&#xff1a; tinyint类型越界测试&#xff1a; bit类型&#xff1a; 小数类型&#xff1a; 字符串类型&#xff1a; …

JAVA中 Lambda 方法引用 算法

算法和数据结构 一、Arrays类 接下来学习的类叫做Arrays&#xff0c;其实Arrays并不是重点&#xff0c;但是我们通过Arrays这个类的学习有助于我们理解下一个知识点Lambda的学习。所以我们这里先学习Arrays&#xff0c;再通过Arrays来学习Lamdba这样学习会更丝滑一些_. 1.1 …

【Qt Quick 项目(第一集Qt Quick UI 项目项目创建)】

# Qt Quick 项目 到底什么是Qt Qml、什么是Qt Quick、QtQuick应用程序与Qt Widget程序有何区别,为了让读者在学习QML之前有一个整体认识,这里先介绍几个Quick项目。 01 Qt Quick UI 项目

Java8的Stream最佳实践

从这一篇文章开始&#xff0c;我们会由浅入深&#xff0c;全面的学习stream API的最佳实践&#xff08;结合我的使用经验&#xff09;&#xff0c;本想一篇写完&#xff0c;但写着写着发现需要写的内容太多了&#xff0c;所以分成一个系列慢慢来说。给大家分享我的经验的同时&a…

【博士每天一篇论文-技术综述】Machine Learning With Echo State Networks 一篇系统讲解ESN知识的五星文章

阅读时间&#xff1a;2023-11-21 1 介绍 年份&#xff1a;2020 作者&#xff1a;徐元超&#xff0c;曼尼托巴大学 期刊&#xff1a; 无 引用量&#xff1a;无 这篇文章是一篇技术报告&#xff0c;从递归神经网络&#xff08;RNNs&#xff09;引入到回声状态网络&#xff08;…

JavaScript基础之JavaScript简介(什么是JavaScript?)

JavaScript简介 JavaScript&#xff0c;通常被缩写为JS&#xff0c;是一种轻量级、跨平台、单线程、解释编译的编程语言。它也被称为网页脚本语言。它以网页开发而闻名&#xff0c;许多非浏览器环境也使用它。 JavaScript是一种弱类型语言。JavaScript可用于客户端开发以及服…

shell编程学习

shell编程学习 变量的高级用法变量替换字符串处理获取字符索引获取子串长度抽取字符串中的子串 案例测试 变量的高级用法 变量替换 ##变量替换&#xff08;贪婪&#xff0c;从前往后匹配&#xff0c;匹配到进行删除&#xff09; test1I love you,you love me echo $test1 han…

Java研学-spring框架(一)

一 概述 1 介绍 Spring框架是一个开源的Java EE应用程序框架&#xff0c;旨在简化Java企业级应用的开发难度和开发周期&#xff0c;主要通过控制反转&#xff08;IoC&#xff09;和面向切面编程&#xff08;AOP&#xff09;等技术实现。   容器&#xff08;Container&#x…

【CVE-2022-22733漏洞复现】

Apache ShardingSphere ElasticJob-UI漏洞 漏洞编号:CVE-2022-22733 文档说明 本文作者:SwBack 创作时间:2024/1/21 19:19:19 知乎:https://www.zhihu.com/people/back-88-87 CSDN:https://blog.csdn.net/qq_30817059 百度搜索: SwBack漏洞描述 Apache ShardingSphere Elast…

Python基础第二篇(Python基础语法)

文章目录 一、字面量二、注释三、变量四、数据类型五、数据类型转换六、标识符七、运算符八、字符串扩展内容&#xff08;1&#xff09;字符串定义&#xff08;2&#xff09;字符串拼接、&#xff08;3&#xff09;字符串格式化&#xff08;4&#xff09;字符串格式化的精度控制…

上位机图像处理和嵌入式模块部署(开篇)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 图像处理是现实生活当中很实用的一门技术。工业上一般采用的是机器视觉&#xff0c;以传统算法和光源控制为主&#xff0c;部分采用了深度学习技术…

MySQL的下载、安装、配置、登录,配置(图+文)(超级详细)

一、 软件的下载 1. 下载地址 官网&#xff1a; https://www.mysql.com 2. 打开官网&#xff0c;点击 DOWNLOADS 然后&#xff0c;点击 MySQL Community(GPL) Downloads 3. 点击 MySQL Community Server 4. 在 General Availability(GA) Releases 中选择适合的版本 …

【C语言基础篇】结构控制(下)转向语句break、continue、goto、return

文章目录 一、break语句 1. break在 while 循环中 2. break在 for 循环中 3. break在 do…while 循环中 4. break在 switch 语句中 5. break 总结 二、continue语句 1. continue在 while 循环中 2. continue在 for 循环中 3. continue在 do...while 循环中 4. con…