Golang context 的作用和实现原理

context的作用

Go语言中的context包提供了一种在进程中跨API和跨进程边界传递取消信号、超时和其他请求范围的值的方式。context的主要作用包括:

取消信号(Cancellation)

  • 当一个操作需要取消时,可以通过context传递一个取消信号。例如,一个长运行的任务可以通过检查context是否被取消来提前终止。

超时(Timeouts)

  • context可以用来设置操作的超时时间。如果操作在指定时间内没有完成,context会发送一个取消信号。

截止时间(Deadlines)

  • 类似于超时,截止时间允许你设置一个绝对时间点,超过这个时间点后,context会发送取消信号。

请求范围的值(Request-scoped values)

  • context可以在处理HTTP请求、数据库事务等过程中,存储请求相关的值,这些值可以跨API边界传递而不需要在每个函数调用中显式传递。

并发控制(Concurrency control)

  • 在并发编程中,context可以用来同步多个goroutine,确保它们在某个操作被取消时能够及时响应。

父子关系(Parent-child relationships)

  • context可以创建父子关系,子context会继承父context的取消信号。这在处理树状结构的并发任务时非常有用。

避免共享可变状态

  • 通过使用context,可以避免在多个goroutine之间共享可变状态,从而减少竞态条件和锁的使用。

API设计

  • context为API设计提供了一种标准的方式来处理取消和超时,使得API的使用更加一致和可预测。

使用context时,通常会创建一个context.Context类型的变量,然后通过context.WithCancelcontext.WithDeadlinecontext.WithTimeoutcontext.WithValue等函数来创建一个新的context,这个新的context会包含额外的信息或取消功能。在实际的函数调用中,context.Context作为第一个参数传递,以确保可以在需要时访问和使用这些值或信号。

context的实现原理:

context.Context

type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key any) any
}

Deadline():

返回过期时间,第一个返回值为过期时间,第二个返回值代表过期时间是否存在。

Done() <-chan struct{}

监测context是否终止,返回值是一个只读的通道,只有当context终止时才能从通道读取到数据。

Err() error

返回错误,通常情况下是context过期或者被手动取消。

Value(key any) any

返回 context 中的对应 key 的值.

context.error

var Canceled = errors.New("context canceled")var DeadlineExceeded error = deadlineExceededError{}type deadlineExceededError struct{}func (deadlineExceededError) Error() string   { return "context deadline exceeded" }
func (deadlineExceededError) Timeout() bool   { return true }
func (deadlineExceededError) Temporary() bool { return true }

Canceled:

context被cancel时报错;

DeadlineExceeded:

context超时时报错

context.emptyCtx

type emptyCtx struct{}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
}

Deadline:

返回一个默认时间和false表示不存在过期时间

Done:

返回一个nil值,用户读取时陷入阻塞状态

Err:

返回一个nil值,表示没有错误

Value:

返回一个nil值,表示没有值

context.Background&&context.TODO

type backgroundCtx struct{ emptyCtx }func (backgroundCtx) String() string {return "context.Background"
}type todoCtx struct{ emptyCtx }func (todoCtx) String() string {return "context.TODO"
}func Background() Context {return backgroundCtx{}
}func TODO() Context {return todoCtx{}
}

context.Background() 和 context.TODO() 方法返回的均是 emptyCtx 类型的一个实例.但是context.TODO()说明可能需要实现更加详细的context

cancelCtx

type canceler interface {cancel(removeFromParent bool, err, cause error)Done() <-chan struct{}
}type cancelCtx struct {Contextmu       sync.Mutex            done     atomic.Value          children map[canceler]struct{} err      error                 cause    error                 
}

Context:

指向有且仅有一个的父节点

        Deadline:

                未实现该方法,不能直接调用

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

        基于 atomic 包,读取 cancelCtx 中的 chan,倘若已存在,则直接返回,否则加互斥锁,然后再次读取,若存在直接返回,否则初始化chan储存到atomic.Value包中,并返回(懒加载:在应用程序初始化时,不会立即加载所有数据,而是等到需要使用数据时才进行加载)

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

        加锁,读取错误,解锁

        Value:
func (c *cancelCtx) Value(key any) any {if key == &cancelCtxKey {return c}return value(c.Context, key)
}

         如果key等于特定值 &cancelCtxKey,则返回cancelCtx自身的指针。否则遵循valueCtx的思路取值返回

mu:

互斥锁,用于并发保护

done:

用于反应生命周期

children:

因为map的value是struct所以,返回的是set而不是map

canceler:

子节点的信息

err:

存放错误信息

cause:

用于提供更详细的错误信息,指示context被取消的具体原因

context.WithCancel()

WithCancel

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {c := withCancel(parent)return c, func() { c.cancel(true, Canceled, nil) }
}func withCancel(parent Context) *cancelCtx {if parent == nil {panic("cannot create context from nil parent")}c := &cancelCtx{}c.propagateCancel(parent, c)return c
}

        首先校验父context不为空,在 propagateCancel 方法内启动一个守护协程,以保证父 context 终止时,该 cancelCtx 也会被终止;返回cancelCtx以及用于终止cancelCtx的闭包函数。

propagateCancel

func (c *cancelCtx) propagateCancel(parent Context, child canceler) {c.Context = parentdone := parent.Done()if done == nil {return // parent is never canceled}select {case <-done:// parent is already canceledchild.cancel(false, parent.Err(), Cause(parent))returndefault:}if p, ok := parentCancelCtx(parent); ok {// parent is a *cancelCtx, or derives from one.p.mu.Lock()if p.err != nil {// parent has already been canceledchild.cancel(false, p.err, p.cause)} else {if p.children == nil {p.children = make(map[canceler]struct{})}p.children[child] = struct{}{}}p.mu.Unlock()return}if a, ok := parent.(afterFuncer); ok {// parent implements an AfterFunc method.c.mu.Lock()stop := a.AfterFunc(func() {child.cancel(false, parent.Err(), Cause(parent))})c.Context = stopCtx{Context: parent,stop:    stop,}c.mu.Unlock()return}goroutines.Add(1)go func() {select {case <-parent.Done():child.cancel(false, parent.Err(), Cause(parent))case <-child.Done():}}()
}

        首先将父context注入子context,如果父context是不会被cancel的类型,则直接返回。如果父context已经被cancel,则直接终止子context,并以父context的err作为子context的err。如果父context是cancelCtx的类型则加锁,并将子context添加到parent的children map当中。如果父context不是cancelCtx的类型,但是具备cancel的能力,则开启一个协程通过多路复用的方式监控 父contxet状态,倘若其终止,则同时终止子context,并透传父contxet的err。

parentCancelCtx

func parentCancelCtx(parent Context) (*cancelCtx, bool) {done := parent.Done()if done == closedchan || done == nil {return nil, false}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
}

如果传入的父context是不能被cancel的节点则返回false,如果用特定的cancelCtxKey取值得到的不是本身,说明不是cancelCtx的类型,返回false(详见之前的Value)

cancel

func (c *cancelCtx) cancel(removeFromParent bool, err, cause error) {if err == nil {panic("context: internal error: missing cancel error")}if cause == nil {cause = err}c.mu.Lock()if c.err != nil {c.mu.Unlock()return // already canceled}c.err = errc.cause = caused, _ := 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, cause)}c.children = nilc.mu.Unlock()if removeFromParent {removeChild(c.Context, c)}
}

        方法有两个入参,第一个表示表示当前 context 是否需要从父 context 的 children set 中删除,第二个表示要返回的具体的错误。

        首先校验是否存在err,若为空则 panic,然后检验cause是否为空,若为空则将传入的err赋值给cause,然后加锁判断当前cancelCtx的err是否为空,若不为空说明已经被cancel,直接返回。否则将传入的err赋值给当前cancelCtx的err,将cause赋值给当前cancelCtx的cause,处理当前cancelCtx的channel,若 channel 此前未初始化则直接注入一个 closedChan,否则关闭channel。遍历当前 cancelCtx 的 children set,依次将 children context 都进行cancel,解锁,根据传入的 removeFromParent flag 判断是否需要手动把 cancelCtx 从 parent 的 children set 中移除。

removeChild

func removeChild(parent Context, child canceler) {if s, ok := parent.(stopCtx); ok {s.stop()return}p, ok := parentCancelCtx(parent)if !ok {return}p.mu.Lock()if p.children != nil {delete(p.children, child)}p.mu.Unlock()
}

timerCtx

type timerCtx struct {cancelCtxtimer *time.Timer // Under cancelCtx.mu.deadline time.Time
}

在cancelCtx的基础上又进行了一层封装。新增了一个 time.Timer 用于定时终止 context;另外新增了一个 deadline 字段用于字段 timerCtx 的过期时间.

timerCtx.Deadline

func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {return c.deadline, true
}

用于展示过期时间

timerCtx.cancel

func (c *timerCtx) cancel(removeFromParent bool, err, cause error) {c.cancelCtx.cancel(false, err, cause)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()
}

复用继承的 cancelCtx 的 cancel 能力,进行 cancel 处理,加锁,停止定时终止context,解锁。

context.WithTimeout & context.WithDeadline

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {return WithDeadline(parent, time.Now().Add(timeout))
}func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {return WithDeadlineCause(parent, d, nil)
}func WithDeadlineCause(parent Context, d time.Time, cause error) (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{deadline: d,}c.cancelCtx.propagateCancel(parent, c)dur := time.Until(d)if dur <= 0 {c.cancel(true, DeadlineExceeded, cause) // deadline has already passedreturn c, func() { c.cancel(false, Canceled, nil) }}c.mu.Lock()defer c.mu.Unlock()if c.err == nil {c.timer = time.AfterFunc(dur, func() {c.cancel(true, DeadlineExceeded, cause)})}return c, func() { c.cancel(true, Canceled, nil) }
}

WithTimeout传入过期所用的时间,WithDeadline传入过期时的时间。

首先检验父context是否为空,检验父context的过期时间是否早于自己,如果早于自己,则直接构造一个cancelCtx即可,否则构造一个timerCtx, 传入过期时间,启动守护方法,同步 parent 的 cancel 事件到子 context,判断过期时间是否已到,如果已到,直接cancel,返DeadlineExceeded错误。加锁,如果当前context的err为空,启动time.Timer,设定一个延时时间,即达到过期时间后会终止该timerCtx,并返回DeadlineExceeded错误,解锁,返回timerCtx,以及封装了cancel逻辑的闭包cancel函数

valueCtx

type valueCtx struct {Contextkey, val any
}

一个 valueCtx 中仅有一组 kv 对

valueCtx.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.Contextcase *cancelCtx:if key == &cancelCtxKey {return c}c = ctx.Contextcase withoutCancelCtx:if key == &cancelCtxKey {// This implements Cause(ctx) == nil// when ctx is created using WithoutCancel.return nil}c = ctx.ccase *timerCtx:if key == &cancelCtxKey {return &ctx.cancelCtx}c = ctx.Contextcase backgroundCtx, todoCtx:return nildefault:return c.Value(key)}}
}

假如当前 valueCtx 的 key 等于用户传入的 key,则直接返回其 value,否则从父context 中依次向上寻找,启动一个 for 循环,由下而上,由子及父,依次对 key 进行匹配,找到匹配的 key,则将该组 value 进行返回。

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

如果父context为空panic,如果key为空panic,如果key的类型无法比较panic,包含父context和kv对,创建一个新的valueCtx并返回。

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

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

相关文章

【超全】目标检测模型分类对比与综述:单阶段、双阶段、有无锚点、DETR、旋转框

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

Llmcad: Fast and scalable on-device large language model inference

题目&#xff1a;Llmcad: Fast and scalable on-device large language model inference 发表于2023.09 链接&#xff1a;https://arxiv.org/pdf/2309.04255 声称是第一篇speculative decoding边缘设备的论文&#xff08;不一定是绝对的第一篇&#xff09;&#xff0c;不开源…

Edge浏览器保留数据,无损降级退回老版本+禁止更新教程(适用于Chrome)

3 个月前阿虚就已经写文章告警过大家&#xff0c;Chromium 内核的浏览器将在 127 以上版本开始限制仍在使用 Manifest V2 规范的扩展&#xff1a;https://mp.weixin.qq.com/s/v1gINxg5vMh86kdOOmqc6A 像是 IDM、油猴脚本管理器、uBblock 等扩展都会受到影响&#xff0c;后续将无…

人工智能零基础入门学习笔记

学习视频&#xff1a;人工智能零基础入门教程 文章目录 1.简介2.应用3.演进4.机器学习5.深度学习6.强化学习7.图像识别8.自然语言9.Python10.Python开发环境11.机器学习算法1.多元线性回归项自实战&#xff1a;糖尿病回归预测 2.逻辑回归3.Softmax回归项目实战&#xff1a;鸢尾…

Spring Boot 3 集成 Spring Security(3)数据管理

文章目录 准备工作新建项目引入MyBatis-Plus依赖创建表结构生成基础代码 逻辑实现application.yml配置SecurityConfig 配置自定义 UserDetailsService创建测试 启动测试 在前面的文章中我们介绍了 《Spring Boot 3 集成 Spring Security&#xff08;1&#xff09;认证》和 《…

Wireshark抓取HTTPS流量技巧

一、工具准备 首先安装wireshark工具&#xff0c;官方链接&#xff1a;Wireshark Go Deep 二、环境变量配置 TLS 加密的核心是会话密钥。这些密钥由客户端和服务器协商生成&#xff0c;用于对通信流量进行对称加密。如果能通过 SSL/TLS 日志文件&#xff08;例如包含密钥的…

Redis(概念、IO模型、多路选择算法、安装和启停)

一、概念 关系型数据库是典型的行存储数据库&#xff0c;存在的问题是&#xff0c;按行存储的数据在物理层面占用的是连续存储空间&#xff0c;不适合海量数据存储。 Redis在生产中使用的最多的是用作数据缓存。 服务器先在缓存中查询数据&#xff0c;查到则返回&#xff0c;…

Cobalt Strike 4.8 用户指南-第十一节 C2扩展

11.1、概述 Beacon 的 HTTP 指标由 Malleable Command and Control &#xff08;Malleable C2&#xff09; 配置文件控制。Malleable C2 配置文件是一个简单的程序&#xff0c;它指定如何转换数据并将其存储在事务中。转换和存储数据的同一程序&#xff08;向后解释&#xff0…

哪里能找到好用的动物视频素材 优质网站推荐

想让你的短视频增添些活泼生动的动物元素&#xff1f;无论是搞笑的宠物瞬间&#xff0c;还是野外猛兽的雄姿&#xff0c;这些素材都能让视频更具吸引力。今天就为大家推荐几个超实用的动物视频素材网站&#xff0c;不论你是短视频新手还是老手&#xff0c;都能在这些网站找到心…

mysql之慢查询设置及日志分析

mysql之慢查询日志分析 1.临时开启慢查询日志2.永久开启慢查询日志 慢查询是指mysql提供的日志记录功能&#xff0c;用来记录执行时间超过设置阈值的sql语句&#xff0c;并将信息写入到日志文件中&#xff1b; 1.临时开启慢查询日志 注意&#xff1a; 1.以下命令需要连接进入到…

【快速入门 LVGL】-- 1、STM32 工程移植 LVGL

目录 一、LVGL 简述 二、复制一个STM32工程 三、下载 LVGL 四、裁剪 源文件 五、工程添加 LVGL 文件 六、注册 显示 七、注册 触摸屏 八、LVGL 心跳、任务刷新 九、开跑 LVGL 十、控件的事件添加、响应处理 十 一、几个好玩小事情 十 二、显示中文 ~~ 约定 ~~ 在…

小程序租赁系统开发的优势与应用解析

内容概要 随着科技的迅猛发展&#xff0c;小程序租赁系统应运而生&#xff0c;成为许多企业优化业务的重要工具。首先&#xff0c;它提升了用户体验。想象一下&#xff0c;用户只需轻轻一点&#xff0c;就能够浏览和租赁心仪的商品&#xff0c;这种便捷的过程使繁琐的操作大大…

LLM应用-prompt提示:RAG query重写、相似query生成 加强检索准确率

参考&#xff1a; https://zhuanlan.zhihu.com/p/719510286 1、query重写 你是一名AI助手&#xff0c;负责在RAG&#xff08;知识库&#xff09;系统中通过重构用户查询来提高检索效果。根据原始查询&#xff0c;将其重写得更具体、详细&#xff0c;以便更有可能检索到相关信…

Spring Boot 3 集成 Spring Security(2)授权

文章目录 授权配置 SecurityFilterChain基于注解的授权控制自定义权限决策 在《Spring Boot 3 集成 Spring Security&#xff08;1&#xff09;》中&#xff0c;我们简单实现了 Spring Security 的认证功能&#xff0c;通过实现用户身份验证来确保系统的安全性。Spring Securit…

C++系统教程008-运算符与表达式

1.运算符与表达式 基本数据类型知道后&#xff0c;就是操作数据。要操作数据&#xff0c;就必须使用运算符和表达式。接下来就是C运算符和表达式的相关知识点&#xff0c; 赋值运算算术运算关系运算逻辑运算逗号运算位运算移位运算sizeof运算数据类型自动转换和强制转换 1.1…

LCR 006. 两数之和 II - 输入有序数组

一.题目&#xff1a; LCR 006. 两数之和 II - 输入有序数组 - 力扣&#xff08;LeetCode&#xff09; 二.我的原始解法-暴力解法超时&#xff1a; class Solution: def twoSum(self, numbers: List[int], target: int) -> List[int]: # 暴力解法 result [] for i in rang…

提供html2canvas+jsPDF将HTML页面以A4纸方式导出为PDF后,内容分页时存在截断的解决思路

前言 最近公司有个系统要做一个质量报告导出为PDF的需求&#xff0c;这个报表的内容是固定格式&#xff0c;但是不固定内容多少的&#xff0c;网上找了很多资料&#xff0c;没有很好的解决我的问题&#xff0c;pdfmakde、还有html2CanvasjsPDF以及Puppeteer无头浏览器的方案都不…

UPLOAD LABS | UPLOAD LABS 靶场初识

关注这个靶场的其它相关笔记&#xff1a;UPLOAD LABS —— 靶场笔记合集-CSDN博客 0x01&#xff1a;UPLOAD LABS 靶场简介 UPLOAD LABS 靶场是一个专门用于学习文件上传漏洞攻击和防御的靶场。它提供了一系列文件上传漏洞的实验环境&#xff0c;用于帮助用户了解文件上传漏洞的…

探索Python词云库WordCloud的奥秘

文章目录 探索Python词云库WordCloud的奥秘1. 背景介绍&#xff1a;为何选择WordCloud&#xff1f;2. WordCloud库简介3. 安装WordCloud库4. 简单函数使用方法5. 应用场景示例6. 常见Bug及解决方案7. 总结 探索Python词云库WordCloud的奥秘 1. 背景介绍&#xff1a;为何选择Wo…

2024年9月中国电子学会青少年软件编程(Python)等级考试试卷(六级)答案 + 解析

一、单选题 1、下面代码运行后出现的图像是&#xff1f;&#xff08; &#xff09; import matplotlib.pyplot as plt import numpy as np x np.array([A, B, C, D]) y np.array([30, 25, 15, 35]) plt.bar(x, y) plt.show() A. B. C. D. 正确答案&#xff1a;A 答案…