Golang之Context详解

引言

之前对context的了解比较浅薄,只知道它是用来传递上下文信息的对象;

对于Context本身的存储、类型认识比较少。

最近又正好在业务代码中发现一种用法:在每个协程中都会复制一份新的局部context对象,想探究下这种写法在性能上有没有弊端。

jobList := []func() error{s.task1,s.task2,s.task3,s.task4,}
if err := gconc.GConcurrency(jobList); err != nil {resource.LoggerService.Error(ctx, "exec concurrency job list error", logit.Error("error", err))
}func (s *Service) task1() (err error) {if !s.isLogin() {return nil}// 新局部变量,值来自全局的context对象ctx := s.ctxreturn nil
}

基本概念

介绍

golang.org/x/net/context,是golang中的一个标准库,主要作用就是创建一个上下文,实现对程序中创建的协程通过传递上下文信息来实现对协程的管理。

img

创建一个Context对象

在Go语言中,可以通过多种方式创建Context:

空Context对象

Background()和TODO():这两个函数分别用于创建空的Context,通常作为根节点使用

  • Background()通常用于main函数、初始化以及测试;
  • TODO()则用于尚未确定使用哪种Context的情况。
可取消的Contex对象

WithCancel(parent Context):创建一个可取消的Context,并返回一个取消函数

  • 当调用取消函数时,会通知所有的子Context,使它们都取消执行。
带有截止时间的Context对象

WithDeadline(parent Context, deadline time.Time):创建一个带有截止时间的Context,并返回一个取消函数

  • 当超过截止时间时,会自动通知所有的子Context,使它们都取消执行;
  • 当超过截止时间时,会自动通知所有的子Context,使它们都取消执行。
带有超时控制的Contex对象

WithTimeout(parent Context, timeout time.Duration):创建一个带有超时控制的Context,它等同于WithDeadline(parent, time.Now().Add(timeout))。

带键值对的Context对象

WithValue(parent Context, key, val interface{}):创建一个带有键值对的Context,同时保留父级Context的所有数据。

  • 需要注意Context主要用于传递请求范围的数据,而不是用于存储大量数据或传递业务逻辑中的参数。

分析Context对象

上面介绍了几种创建Context对象的方法,包括创建可取消的Context、带有截止时间的Context以及带有键值对的Context

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

Context接口定义了四个方法:

  1. Deadline():返回该context被取消的时间,当没有设置截止日期时,返回ok==false。
  2. Done():返回一个只读的channel。当context被取消或超时时,此channel会被关闭,通知goroutine不再继续执行。
  3. Err():如果Done尚未关闭,返回nil;如果context已被取消或超时,返回取消或超时的错误。
  4. Value(key interface{}) interface{}:从context中获取与key相关联的值。通常用于传递一些请求范围内的变量,如用户认证信息、跟踪请求ID等。但需要注意的是,不应滥用此功能传递业务逻辑中的参数。
不同类型的Context

通过分析Context接口,可以知道Context对象都是对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
}

而带有超时控制的Context其实就是一个带有定时器并且实现了Context接口的对象

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

Context的作用

控制子协程

对于存在若干个协程的程序,协程之前可能会存在如下的关系,这就需要在父协程关闭时,对子协程及时关闭;

否则协程可能会持续存在与内存中,造成内存泄漏。

img

Context对子协程的控制销毁就是基于协程创建的过程中,为每个子协程创建子context,以WithCancel()方法为例进行分析:

WithCancel()会返回一个新的子context和一个上下文取消方法,当执行cancel时,当前协程下的子context都会被销毁。

package mainimport ("context""fmt""time"
)// worker 是一个模拟工作的函数,它接受一个 context 并根据 context 的状态来决定是否继续工作。
func worker(ctx context.Context, id int) {for {select {case <-ctx.Done():// 当 context 被取消时,worker 会收到通知并退出循环。fmt.Printf("Worker %d: stopping\n", id)returndefault:// 继续执行模拟的工作。fmt.Printf("Worker %d: working\n", id)time.Sleep(1 * time.Second)}}
}func main() {// 创建一个带取消功能的 context。ctx, cancel := context.WithCancel(context.Background())// 启动多个 worker 协程。for i := 1; i <= 3; i++ {go worker(ctx, i)}// 让主协程等待一段时间,然后取消 context。time.Sleep(5 * time.Second)fmt.Println("Main: canceling context")cancel()// 等待一段时间以确保所有 worker 都有机会响应取消信号。// 在实际应用中,你可能需要一个更复杂的机制来等待所有 worker 退出。time.Sleep(2 * time.Second)fmt.Println("Main: exiting")
}

传递上下文信息

通常使用带键值对的Contex对象,即ValueContext传递信息。

package mainimport ("context""fmt""time"
)// requestIDKey 是一个用于在 context 中存储请求 ID 的键。
type requestIDKey struct{}// worker 是一个模拟工作的函数,它接受一个 context 并从中提取请求 ID。
func worker(ctx context.Context, taskName string) {// 从 context 中获取请求 ID。requestID := ctx.Value(requestIDKey{}).(string)fmt.Printf("%s: started, request ID: %s\n", taskName, requestID)// 模拟工作。time.Sleep(2 * time.Second)// 完成工作。fmt.Printf("%s: completed, request ID: %s\n", taskName, requestID)
}func main() {// 创建一个带有请求 ID 的 context。requestID := "12345"ctx := context.WithValue(context.Background(), requestIDKey{}, requestID)// 启动多个 worker 协程。tasks := []string{"Task A", "Task B", "Task C"}for _, task := range tasks {go worker(ctx, task)}// 等待一段时间以确保所有 worker 都有机会完成工作。// 在实际应用中,你可能需要一个更复杂的机制来等待所有 worker 退出。time.Sleep(6 * time.Second)fmt.Println("Main: all tasks completed or timed out")
}

额外补充:Context变量的复制

写一个示例代码,通过debug来分析

type UserInfo struct {UID     intName    stringAddress *Address
}
type Address struct {X intY int
}func TestValueContext(t *testing.T) {ctx := context.Background()address := &Address{X: 101,Y: 202,}withValue := context.WithValue(ctx, UserInfo{}, UserInfo{UID:     1,Name:    "test",Address: address,})// 新变量 拷贝的context对象copyCtx := withValuefmt.Println(copyCtx)
}

通过debug可以看到,和普通的变量赋值一样,拷贝出的copyCtx对象就是ctx对象的值;

拷贝的过程是浅拷贝,当ctx中包含指针时,拷贝的是其地址。

img

最开始的问题-拷贝Context的意义?

通过上面的分析我们可以知道以下几点事实

  1. Context的拷贝是针对具体实现了Context接口的对象,因为接口无法拷贝;
  2. Context对象的拷贝是浅拷贝,和普通的变量一样;
  3. 需要基于一个Context实现 添加信息、并发控制、并发安全等功能,需要使用Context库提供的方法,普通的拷贝没有意义。

因此,问题代码中对ctx的拷贝,不考虑代码清晰度的情况下,并没有额外的意义,而且在被拷贝的Context对象很大时,会有额外的内存开销。

func (s *Service) task1() (err error) {if !s.isLogin() {return nil}// 新局部变量,值来自全局的context对象ctx := s.ctxreturn nil
}

参考

Go语言高并发系列三:context - 掘金

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

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

相关文章

AIGC浪潮下,图文内容社区数据指标体系如何构建?

文章目录 01 案例&#xff1a;以图文内容社区为例实践数据指标体构建02 4个步骤实现数据指标体系构建1. 明确业务目标&#xff0c;梳理北极星指标2. 梳理业务流程&#xff0c;明确过程指标3. 指标下钻分级&#xff0c;构建多层级数据指标体系4. 添加分析维度&#xff0c;构建完…

数据结构:二叉树

目录 一、树型结构 1、基本概念 2、重要概念 3、树的表示形式 二、二叉树 1、概念 2、两种特殊的二叉树 3、二叉树的性质 4、二叉树的存储 5、二叉树的遍历 二叉树的构建 &#xff08;1&#xff09;前序遍历 &#xff08;2&#xff09;中序遍历 &#xff08;3&am…

SpringBoot项目中的异常处理

定义错误页面 SpringBoot 默认的处理异常的机制&#xff1a;SpringBoot 默认的已经提供了一套处理异常的机制。一旦程序中出现了异常 SpringBoot 会像/error 的 url 发送请求。在 springBoot 中提供了一个叫 BasicExceptionController 来处理/error 请求&#xff0c;然后跳转到…

《安富莱嵌入式周报》第349期:VSCode正式支持Matlab调试,DIY录音室级麦克风,开源流体吊坠,物联网在军工领域的应用,Unicode字符压缩解压

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; 《安富莱嵌入式周报》第349期&#xff1a;VSCode正式支持Matlab调试&#xff0c;DIY录音室级麦克风…

C++priority_queue模拟实现

Cpriority_queue模拟实现 1.priority_queue基本概念2.priority_queue基本结构3.size()成员函数4.empty()成员函数5.top()成员函数6.push()成员函数7.pop()成员函数8.构造函数9.完整代码 &#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#…

[STM32 HAL库]串口中断编程思路

一、前言 最近在准备蓝桥杯比赛&#xff08;嵌入式赛道&#xff09;&#xff0c;研究了以下串口空闲中断DMA接收不定长的数据&#xff0c;感觉这个方法的接收效率很高&#xff0c;十分好用。方法配置都成功了&#xff0c;但是有一个点需要进行考虑&#xff0c;就是一般我们需要…

嵌入式 工程配置

本次用的STM32F4芯片系列 目录 1. 新建文件夹 2. 新建文件夹下创建 3. 打开keil5 3.1.1 点击菜单栏project 点击new project 3.1.2. 选择刚刚新建的文件夹 3.1.3.将项目文件保存到Project文件夹里 3.1.4. 将项目命名这里命名为STM32 保存 3.1.5. 保存好后会跳出选择芯…

我的图形布局 组织结构图布局

组织结构图布局,有的人也叫它树状布局,在图形中是经常用到的布局算法.形成类似如下图的图形布局方式 首先创建一个类, public class TreeLayouter {private int m_space 40;/// <summary>/// 空间间隔/// </summary>public int Space{get { return m_space; }se…

计算机网络介质访问控制全攻略:从信道划分到协议详解!!!

一、信道划分介质访问控制 介质访问控制&#xff1a;多个节点共享同一个“总线型”广播信道时&#xff0c;可能发生“信号冲突” 应该怎么控制各节点对传输介质的访问&#xff0c;才能减少冲突&#xff0c;甚至避免冲突? 时分复用(TDM) 时分复用&#xff1a;将时间分为等长的“…

sql主从同步

今天给大家介绍两种mysql的主从同步方式&#xff1a;第一种是基于binlogzhu主从同步&#xff1b;第二种就是基于gtid的主从同步方式。 首先给大家介绍一下什么是sql的主从复制。 主从复制&#xff1a; 通过将MySQL的某一台主机&#xff08;master&#xff09;的数据复制到其…

计算机组成原理——数据表示(二)

当生活的压力和困惑缠绕在身边&#xff0c;我们往往需要振奋精神&#xff0c;勇往直前。无论在何种困境中&#xff0c;我们都要保持积极的态度和坚定的信念。将悲观的情绪抛之脑后&#xff0c;展现出坚强的意志力和无尽的活力。振奋精神意味着我们要战胜自己内心的负面情绪&…

Spring Boot整合Thymeleaf、JDBC Template与MyBatis配置详解

本文将详细介绍如何在Spring Boot项目中整合Thymeleaf模板引擎、JDBC Template和MyBatis&#xff0c;涵盖YAML配置、依赖版本匹配、项目结构设计及代码示例。 一、版本兼容性说明 Spring Boot版本与Java版本对应关系 Spring Boot 2.x&#xff1a;支持Java 8、11&#xff08;推…

概率论里的特征函数,如何用卷积定理去理解

概率论里的特征函数&#xff0c;如何用卷积定理去理解_哔哩哔哩_bilibili

论文笔记(六十二)Diffusion Reward Learning Rewards via Conditional Video Diffusion

Diffusion Reward Learning Rewards via Conditional Video Diffusion 文章概括摘要1 引言2 相关工作3 前言4 方法4.1 基于扩散模型的专家视频建模4.2 条件熵作为奖励4.3 训练细节 5 实验5.1 实验设置5.2 主要结果5.3 零样本奖励泛化5.4 真实机器人评估5.5 消融研究 6 结论 文章…

HashMap用法

一、构造方法 构造方法有4个。 1、手动声明初始容量及负载因子的构造函数。初容容量的最大值不能超过MAXIMUM_CAPACITY 2、手动声明初始容量的构造函数&#xff0c;负载因子是默认大小。 默认的负载因子是0.75 3、无参的构造函数&#xff0c;会指定默认的负载因子。容量是默…

Java基础 (一)

基础概念及运算符、判断、循环 基础概念 关键字 数据类型 分为两种 基本数据类型 标识符 运算符 运算符 算术运算符 隐式转换 小 ------>>> 大 强制转换 字符串 拼接符号 字符 运算 自增自减运算符 ii赋值运算符 赋值运算符 包括 强制转换 关系运算符 逻辑运算符 …

无人机在城市执法监管中的应用:技术革新与监管挑战

随着科技的不断进步&#xff0c;无人机技术在城市管理中的应用越来越广泛。无人机以其灵活性、高效性和低成本的优势&#xff0c;正在逐渐成为城市执法监管的得力助手。本文将探讨无人机在城市执法监管中的应用现状、技术优势以及面临的挑战。 无人机技术在城市执法监管中的应用…

AI模型提示词(prompt)优化-实战(一)

一、prompt作用 用户与AI模型沟通的核心工具&#xff0c;用于引导模型生成特定内容、控制输出质量、调整行为模式&#xff0c;并优化任务执行效果&#xff0c;从而提升用户体验和应用效果 二、prompt结构 基本结构 角色&#xff1a;设定一个角色&#xff0c;给AI模型确定一个基…

mac 电脑上安装adb命令

在Mac下配置android adb命令环境&#xff0c;配置方式如下&#xff1a; 1、下载并安装IDE &#xff08;android studio&#xff09; Android Studio官网下载链接 详细的安装连接请参考 Mac 安装Android studio 2、配置环境 在安装完成之后&#xff0c;将android的adb工具所在…

电子应用设计方案101:智能家庭AI喝水杯系统设计

智能家庭 AI 喝水杯系统设计 一、引言 智能家庭 AI 喝水杯系统旨在为用户提供个性化的饮水提醒和健康管理服务&#xff0c;帮助用户养成良好的饮水习惯。 二、系统概述 1. 系统目标 - 精确监测饮水量和饮水频率。 - 根据用户的身体状况和活动量&#xff0c;智能制定饮水计划。…