Golang之Context详解

news2025/1/29 6:24:20

引言

之前对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.ctx
	return 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() error
    
	Value(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 {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

Context的作用

控制子协程

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

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

img

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

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

package main
 
import (
	"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)
			return
		default:
			// 继续执行模拟的工作。
			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 main

import (
	"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     int
	Name    string
	Address *Address
}
type Address struct {
	X int
	Y 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 := withValue
	fmt.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.ctx
	return nil
}

参考

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

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

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

相关文章

VSCode+Continue实现AI辅助编程

Continue是一款功能强大的AI辅助编程插件&#xff0c;可连接多种大模型&#xff0c;支持代码设计优化、错误修正、自动补全、注释编写等功能&#xff0c;助力开发人员提高工作效率与代码质量。以下是其安装和使用方法&#xff1a; 一、安装VSCode 参见&#xff1a; vscode安…

Python 在Word中添加、或删除超链接

在Word文档中&#xff0c;超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能。通过添加超链接&#xff0c;用户可以轻松地导航到相关信息&#xff0c;从而增强文档的互动性和可读性。本文将介绍如何使用Python在Word中添加超链接、或删除Word文档中的超…

Oracle迁移DM数据库

Oracle迁移DM数据库 本文记录使用达梦官方数据迁移工具DTS&#xff0c;将Oracle数据库的数据迁移至达梦数据库。 1 数据准备 2 DTS工具操作步骤 2.1 创建工程 打开DTS迁移工具&#xff0c;点击新建工程&#xff0c;填写好工程信息&#xff0c;如图&#xff1a; 2.2 新建迁…

Spring Boot整合JavaMail实现邮件发送

一. 发送邮件原理 发件人【设置授权码】 - SMTP协议【Simple Mail TransferProtocol - 是一种提供可靠且有效的电子邮件传输的协议】 - 收件人 二. 获取授权码 开通POP3/SMTP&#xff0c;获取授权码 授权码是QQ邮箱推出的&#xff0c;用于登录第三方客户端的专用密码。适用…

编辑器Vim基本模式和指令 --【Linux基础开发工具】

文章目录 一、编辑器Vim 键盘布局二、Linux编辑器-vim使用三、vim的基本概念正常/普通/命令模式(Normal mode)插入模式(Insert mode)末行模式(last line mode) 四、vim的基本操作五、vim正常模式命令集插入模式从插入模式切换为命令模式移动光标删除文字复制替换撤销上一次操作…

K8S极简教程(4小时快速学会)

1. K8S 概览 1.1 K8S 是什么 K8S官网文档&#xff1a;https://kubernetes.io/zh/docs/home/ 1.2 K8S核心特性 服务发现与负载均衡&#xff1a;无需修改你的应用程序即可使用陌生的服务发现机制。存储编排&#xff1a;自动挂载所选存储系统&#xff0c;包括本地存储。Secret和…

淘宝商品数据解析的应用场景有哪些?

淘宝商品数据解析在多个领域有着广泛的应用场景&#xff0c;以下为你详细介绍&#xff1a; 电商运营与营销 选品分析&#xff1a;通过解析淘宝商品数据&#xff0c;卖家可以了解不同商品的销售情况、价格区间、市场需求热度等信息。例如分析某类商品在不同季节的销量变化&#…

基于OpenCV实现的答题卡自动判卷系统

一、图像预处理 🌄 二、查找答题卡轮廓 📏 三、透视变换 🔄 四、判卷与评分 🎯 五、主函数 六、完整代码+测试图像集 总结 🌟 在这篇博客中,我将分享如何使用Python结合OpenCV库开发一个答题卡自动判卷系统。这个系统能够自动从扫描的答题卡中提取信…

计网week1+2

计网 一.概念 1.什么是Internet 节点&#xff1a;主机及其运行的应用程序、路由器、交换机 边&#xff1a;通信链路&#xff0c;接入网链路主机连接到互联网的链路&#xff0c;光纤、网输电缆 协议&#xff1a;对等层的实体之间通信要遵守的标准&#xff0c;规定了语法、语义…

如何使用tushare pro获取股票数据——附爬虫代码以及tushare积分获取方式

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据 总结 一、Tushare 介绍 Tushare 是一个提供中国股市数据的API接口服务&#xff0c;它允许用户…

vim的多文件操作

[rootxxx ~]# vim aa.txt bb.txt cc.txt #多文件操作 next #下一个文件 prev #上一个文件 first #第一个文件 last #最后一个文件 快捷键: ctrlshift^ #当前和上个之间切换 说明&#xff1a;快捷键ctrlshift^&#xff0c…

Mac m1,m2,m3芯片使用nvm安装node14报错

使用nvm安装了node 12/16/18都没有问题&#xff0c;到14就报错了。第一次看到这个报错有点懵&#xff0c;查询资料发现是Mac芯片的问题。 Issue上提供了两个方案&#xff1a; 1、为了在arm64的Mac上安装node 14&#xff0c;需要使用Rosseta&#xff0c;可以通过以下命令安装 …

【云安全】云原生-Docker(五)容器逃逸之漏洞利用

漏洞利用逃逸 通过漏洞利用实现逃逸&#xff0c;主要分为以下两种方式&#xff1a; 1、操作系统层面的内核漏洞 这是利用宿主机操作系统内核中的安全漏洞&#xff0c;直接突破容器的隔离机制&#xff0c;获得宿主机的权限。 攻击原理&#xff1a;容器本质上是通过 Linux 的…

JAVA设计模式:依赖倒转原则(DIP)在Spring框架中的实践体现

文章目录 一、DIP原则深度解析1.1 核心定义1.2 现实比喻 二、Spring中的DIP实现机制2.1 传统实现 vs Spring实现对比 三、Spring中DIP的完整示例3.1 领域模型定义3.2 具体实现3.3 高层业务类3.4 配置类 四、Spring实现DIP的关键技术4.1 依赖注入方式对比4.2 自动装配注解 五、D…

基于微信小程序的健身管理系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

【机器学习】深入探索SVM:支持向量机的原理与应用

目录 &#x1f354; SVM引入 1.1什么是SVM? 1.2支持向量机分类 1.3 线性可分、线性和非线性的区分 &#x1f354; 小结 学习目标 知道SVM的概念 &#x1f354; SVM引入 1.1什么是SVM? 看一个故事&#xff0c;故事是这样子的&#xff1a; 在很久以前的情人节&#xf…

输入带空格的字符串,求单词个数

输入带空格的字符串&#xff0c;求单词个数 __ueooe_eui_sjje__ ---->3syue__jdjd____die_ ---->3shuue__dju__kk ---->3 #include <stdio.h> #include <string.h>// 自定义函数来判断字符是否为空白字符 int isSpace(char c) {return c || c \t || …

[STM32 - 野火] - - - 固件库学习笔记 - - -十二.基本定时器

一、定时器简介 STM32 中的定时器&#xff08;TIM&#xff0c;Timer&#xff09;是其最重要的外设之一&#xff0c;广泛用于时间管理、事件计数和控制等应用。 1.1 基本功能 定时功能&#xff1a;TIM定时器可以对输入的时钟进行计数&#xff0c;并在计数值达到设定值时触发中…

kaggle比赛入门 - House Prices - Advanced Regression Techniques(第二部分)

本文承接上一篇 1. 分析住宅类型&#xff08;BldgType&#xff09;的分布以及它们与销售价格&#xff08;SalePrice&#xff09;的关系 # 1. distribution of dwelling types and their relation to sale prices # BldgType: Type of dwellingdwelling_types df[BldgType].v…

数字图像处理:实验六

uu们&#xff01;大家好&#xff0c;2025年的新年就要到来&#xff0c;咸鱼哥在这里祝大家在2025年每天开心快乐&#xff0c;天天挣大钱&#xff0c;自由自在&#xff0c;健健康康&#xff0c;万事如意&#xff01;&#xff08;要是咸鱼哥嘴笨的话&#xff0c;还望大家多多包涵…