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.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 - 掘金

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从桌面到前端:效率与渲染优化的技术进化20250122

从桌面到前端&#xff1a;效率与渲染优化的技术进化 在应用开发的广袤天地中&#xff0c;我们见证了从传统桌面开发&#xff08;如 MFC、PyQt&#xff09;向现代 Web 前端框架&#xff08;如 React、Vue&#xff09;的华丽转变。这一变革犹如一场技术革命&#xff0c;带来了开…

web服务器 网站部署的架构

WEB服务器工作原理 Web web是WWW(World Wide Web)的简称&#xff0c;基本原理是&#xff1a;请求(客户端)与响应(服务器端)原理&#xff0c;由遍布在互联网中的Web服务器和安装了Web浏览器的计算机组成 客户端发出请求的方式&#xff1a;地址栏请求、超链接请求、表单请求 …

快速构建springboot+vue后台管理系统

项目介绍 1.需求定义&#xff1a;外包项目如雨后春笋&#xff0c;开发工期被迫压缩&#xff0c;为了开发人员专注开发项目业务&#xff0c;早点下班能陪老婆、孩子。 2.产品定位: 简约后台管理系统 3.项目特点&#xff1a;此项目代码清晰、界面简洁、springboot layuiadmin 构…

C语言--数据在内存中的存储

数据在内存中的存储 主要研究整型和浮点型在内存中的存储。 1. 整数在内存中的存储 在学习操作符的时候&#xff0c;就了解过了下面的内容&#xff1a; 整数的2进制表示方法有三种&#xff0c;即原码、反码和补码。 有符号的整数&#xff0c;三种表示方法均有符号位和数值…

HTB:Sauna[WriteUP]

目录 连接至HTB服务器并启动靶机 信息收集 使用rustscan对靶机TCP端口进行开放扫描 将靶机TCP开放端口号提取并保存 使用nmap对靶机TCP开放端口进行脚本、服务扫描 使用nmap对靶机TCP开放端口进行漏洞、系统扫描 使用nmap对靶机常用UDP端口进行开放扫描 使用nmap对靶机…

wireshark工具简介

目录 1 wireshark介绍 2 wireshark抓包流程 2.1 选择网卡 2.2 停止抓包 2.3 保存数据 3 wireshark过滤器设置 3.1 显示过滤器的设置 3.2 抓包过滤器 4 wireshark的封包列表与封包详情 4.1 封包列表 4.2 封包详情 参考文献 1 wireshark介绍 wireshark是非常流行的网络…

2025.1.20——一、[RCTF2015]EasySQL1 二次注入|报错注入|代码审计

题目来源&#xff1a;buuctf [RCTF2015]EasySQL1 目录 一、打开靶机&#xff0c;整理信息 二、解题思路 step 1&#xff1a;初步思路为二次注入&#xff0c;在页面进行操作 step 2&#xff1a;尝试二次注入 step 3&#xff1a;已知双引号类型的字符型注入&#xff0c;构造…

kong 网关和spring cloud gateway网关性能测试对比

该测试只是简单在同一台机器设备对spring cloud gateway网关和kong网关进行对比&#xff0c;受限于笔者所拥有的资源&#xff0c;此处仅做简单评测。 一、使用spring boot 的auth-service作为服务提供者 该服务提供了一个/health接口&#xff0c;接口返回"OK"&…

winfrom项目,引用EPPlus.dll实现将DataTable 中的数据保存到Excel文件

最近研究不安装office也可以保存Excel文件&#xff0c;在网上查询资料找到这个方法。 第一步&#xff1a;下载EPPlus.dll文件&#xff08;自行去网上搜索下载&#xff09; 第二步&#xff1a;引用到需要用的项目中&#xff0c;如图所示&#xff1a; 第三步&#xff1a;写代码…

框架层实现cpu高负载(cpuload)的检测方案

摘要 这是2018年在小厂的老方案了&#xff0c;现在看方案已经过时了也不太合理&#xff0c;仅供参考&#xff0c;上层框架开启一个5分钟定时器&#xff0c;检测5分钟内总cpu负载和每个线程cpu负载情况&#xff0c;当检测到cpu负载大于绿盟性能或功耗定义的阈值时&#xff0c;结…

Android BitmapShader简洁实现马赛克,Kotlin(一)

Android BitmapShader简洁实现马赛克&#xff0c;Kotlin&#xff08;一&#xff09; 这一篇&#xff0c; Android使用PorterDuffXfermode模式PorterDuff.Mode.SRC_OUT橡皮擦实现马赛克效果&#xff0c;Kotlin&#xff08;3&#xff09;-CSDN博客 基于PorterDuffXfermode实现马…

人工智能在数字化转型中的角色:从数据分析到智能决策

引言 在数字化转型浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;正迅速崛起&#xff0c;成为推动企业创新和变革的关键力量。面对日益复杂的市场环境和激烈的行业竞争&#xff0c;企业亟需借助技术手段提高运营效率、优化决策过程&#xff0c;并增强市场竞争力。而AI…

「全网最细 + 实战源码案例」设计模式——工厂方法模式

核心思想 简单工厂模式是一种创建者模式&#xff0c;它通过一个工厂类负责创建不同类型的对象&#xff0c;根据传入的参数决定实例化的具体类&#xff0c;也被称为“静态工厂方法”模式&#xff0c;因为工厂方法通常是静态的。 结构 1. 工厂类&#xff1a; 提供一个静态方法…

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

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

Golang:使用DuckDB查询Parquet文件数据

本文介绍DuckDB查询Parquet文件的典型应用场景&#xff0c;掌握DuckDB会让你的产品分析能力更强&#xff0c;相反系统运营成本相对较低。为了示例完整&#xff0c;我也提供了如何使用Python导出MongoDB数据。 Apache Parquet文件格式在存储和传输大型数据集方面变得非常流行。最…

Rust Actix Web 项目实战教程 mysql redis swagger:构建用户管理系统

Rust Actix Web 项目实战教程&#xff1a;构建用户管理系统 项目概述 本教程将指导你使用 Rust 和 Actix Web 构建一个完整的用户管理系统&#xff0c;包括数据库交互、Redis 缓存和 Swagger UI 文档。 技术栈 Rust 编程语言Actix Web 框架SQLx (MySQL 数据库)Redis 缓存Uto…

校园网上店铺的设计与实现(代码+数据库+LW)

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统校园店铺商品销售信息管理难度大&#xff0c;容错率低&a…

生成对抗网络(GAN)入门与编程实现

生成对抗网络&#xff08;Generative Adversarial Networks, 简称 GAN&#xff09;自 2014 年由 Ian Goodfellow 等人提出以来&#xff0c;迅速成为机器学习和深度学习领域的重要工具之一。GAN 以其在图像生成、风格转换、数据增强等领域的出色表现&#xff0c;吸引了广泛的研究…

26、正则表达式

目录 一. 匹配字符 .&#xff1a;匹配除换行符外的任意单个字符。 二. 位置锚点 ^&#xff1a;匹配输入字符串的开始位置。 $&#xff1a;匹配输入字符串的结束位置。 \b&#xff1a;匹配单词边界。 \B&#xff1a;匹配非单词边界。 三. 重复限定符 *&#xff1a;匹配…

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

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