第 17 章 - Go语言 上下文( Context )

在Go语言中,context包为跨API和进程边界传播截止时间、取消信号和其他请求范围值提供了一种方式。它主要应用于网络服务器和长时间运行的后台任务中,用于控制一组goroutine的生命周期。下面我们将详细介绍context的定义、使用场景、取消和超时机制,并通过案例和源码解析来加深理解。

Context的定义

context.Context接口定义如下:

type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key interface{}) interface{}
}
  • Deadline()返回一个时间点,表示这个请求的截止时间。如果返回的okfalse,则没有设置截止时间。
  • Done()返回一个通道,当请求应该被取消时,这个通道会关闭。通常用于监听取消或超时事件。
  • Err()返回导致Done通道关闭的原因。如果Done尚未关闭,则返回nil
  • Value()用于传递请求范围内的数据,如用户身份验证信息等。它不应该用于传递可变状态。

使用场景

context主要用于以下场景:

  • 当处理HTTP请求时,可以将请求的上下文信息传递给处理函数及其调用的所有子goroutine。
  • 在长时间运行的任务中,可以通过context来传递取消信号,以便优雅地终止任务。
  • 当需要设置操作的超时时,可以使用带有超时功能的context

取消和超时

取消

取消context可以通过创建一个可取消的context实现,例如使用context.WithCancel函数:

ctx, cancel := context.WithCancel(context.Background())
defer cancel() // 确保在函数退出时调用cancel

调用cancel()函数后,ctx.Done()返回的通道会被关闭,所有监听该通道的goroutine都会收到取消信号。

超时

对于需要设置超时的情况,可以使用context.WithTimeout

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

在这个例子中,5秒后ctx.Done()通道会自动关闭,发送超时信号。

案例

假设我们有一个HTTP服务器,需要处理每个请求的最大时间为2秒,超过这个时间就取消请求:

func handler(w http.ResponseWriter, r *http.Request) {ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)defer cancel()select {case <-time.After(1 * time.Second):fmt.Fprintf(w, "Request processed successfully")case <-ctx.Done():fmt.Fprintf(w, "Request processing timed out: %v", ctx.Err())}
}func main() {http.HandleFunc("/", handler)http.ListenAndServe(":8080", nil)
}

源码分析

context包中的核心是几个实现了Context接口的结构体,如cancelCtxtimerCtx等。以timerCtx为例,它是select语句中的定时器实现的基础,用于处理超时情况。timerCtx内部维护了一个time.Timer对象,当超时发生时,会关闭Done通道。

type timerCtx struct {cancelCtxtimer *time.Timer // Underlying timer.// ...
}// WithTimeout returns WithCancel(parent, Background()) and starts a Timer running.
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {if timeout <= 0 {return WithDeadline(parent, time.Time{})}c := &timerCtx{cancelCtx: newCancelCtx(parent),deadline:  time.Now().Add(timeout),}// ...c.mu.Lock()defer c.mu.Unlock()if c.err == nil {c.timer = time.AfterFunc(timeout, func() {c.cancel(true, DeadlineExceeded)})}return c, c.Cancel
}

以上就是关于Go语言context的基本介绍、使用场景、取消和超时机制的详解,以及简单的案例和源码分析。希望这些信息能够帮助你更好地理解和使用context

当然,我们可以进一步深入探讨context的高级使用技巧、最佳实践以及一些常见的误区。

高级使用技巧

值传递

context允许通过Value方法传递请求特定的数据。这是一种轻量级的机制,用于在请求的处理链中传递信息,比如用户认证信息、请求ID等。但是需要注意的是,Value应该仅用于传递请求作用域的数据,而不是作为全局变量的替代品。

示例代码:

type key intconst (userKey key = 0
)func middleware(next http.HandlerFunc) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {user := getUserFromDatabase(r) // 假设从数据库获取用户信息ctx := context.WithValue(r.Context(), userKey, user)next.ServeHTTP(w, r.WithContext(ctx))}
}func handler(w http.ResponseWriter, r *http.Request) {user := r.Context().Value(userKey)if user != nil {fmt.Fprintf(w, "Hello, %s!", user.(string))} else {http.Error(w, "User not found", http.StatusUnauthorized)}
}
多个context的组合

有时候你可能需要组合多个context,例如同时设置超时和取消。这可以通过嵌套调用WithCancelWithTimeout来实现。

示例代码:

parentCtx := context.Background()
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel()ctx, cancel = context.WithCancel(ctx)
defer cancel()

最佳实践

  1. 避免过度使用Value:虽然Value方法非常方便,但过度使用可能会导致代码难以维护。应该只传递真正需要的信息。

  2. 及时释放资源:使用defer确保cancel函数总是被调用,这样可以避免资源泄露。

  3. 不要在context中存储大量数据context中的数据应该是轻量级的,避免存储大对象。

  4. 避免直接使用context.Backgroundcontext.TODO:在实际应用中,应该根据具体需求创建适当的context

常见误区

  1. 误用context.TODOcontext.TODO是一个占位符,用于表示将来会提供一个合适的context。在生产代码中,应该使用具体的context,而不是TODO

  2. 忽略Done通道:在处理长时间运行的任务时,应该始终监听Done通道,以便在接收到取消信号时能够及时停止任务。

  3. 错误地传递contextcontext应该从请求的入口点传递到所有需要它的组件,而不仅仅是部分组件。

源码深入

让我们更深入地看看context的一些内部实现细节。以cancelCtx为例,这是context中最基本的可取消类型。

type cancelCtx struct {Contextmu         sync.Mutex            // protects following fieldsdone       atomic.Value          // of chan struct{}, created lazily, closed by first cancel callchildren   map[canceler]struct{} // set to nil by the first cancel callerr        error                 // set to non-nil by the first cancel call
}func (c *cancelCtx) Done() <-chan struct{} {c.mu.Lock()if c.done == nil {c.done = make(chan struct{})}d := c.done.Load().(chan struct{})c.mu.Unlock()return d
}func (c *cancelCtx) Err() error {c.mu.Lock()err := c.errc.mu.Unlock()return err
}func (c *cancelCtx) cancel(removeFromParent bool, err error) {if err == nil {panic("context: internal error: missing cancel error")}c.mu.Lock()if c.err != nil {c.mu.Unlock()return}c.err = errif c.done == nil {c.done = closedchan} else {close(c.done.Load().(chan struct{})) // ignore multiple closes}for child := range c.children {// Notify child that it has been canceled.child.cancel(false, err)}c.children = nilc.mu.Unlock()if removeFromParent {removeChild(c.Context, c)}
}
  • Done方法返回一个通道,该通道在cancel被调用时关闭。
  • Err方法返回导致Done通道关闭的错误。
  • cancel方法用于关闭Done通道并通知所有子context

通过上述源码,我们可以看到context是如何管理其生命周期和子context的。

总结

context是Go语言中处理并发、超时和取消信号的重要工具。正确使用context可以使你的程序更加健壮和高效。

我们可以进一步探讨一些更高级的主题,包括如何在实际项目中更好地使用context,以及一些常见问题的解决方案。

实际项目中的最佳实践

1. 统一处理取消和超时

在实际项目中,经常会遇到需要同时处理取消和超时的情况。可以通过组合WithTimeoutWithCancel来实现这一点。

func processRequest(ctx context.Context) error {// 创建一个带超时的上下文ctx, cancel := context.WithTimeout(ctx, 5*time.Second)defer cancel()// 执行耗时操作select {case <-time.After(3 * time.Second):return nilcase <-ctx.Done():return ctx.Err()}
}
2. 在中间件中使用context

在Web框架中,中间件是处理请求的常用模式。通过在中间件中传递context,可以确保每个请求的上下文信息在整个处理链中都能被访问到。

func loggingMiddleware(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {start := time.Now()ctx := context.WithValue(r.Context(), "startTime", start)r = r.WithContext(ctx)next.ServeHTTP(w, r)log.Printf("Request took %v", time.Since(start))})
}func handler(w http.ResponseWriter, r *http.Request) {startTime := r.Context().Value("startTime").(time.Time)// 处理请求time.Sleep(2 * time.Second)w.Write([]byte(fmt.Sprintf("Request started at %v", startTime)))
}func main() {http.Handle("/", loggingMiddleware(http.HandlerFunc(handler)))http.ListenAndServe(":8080", nil)
}
3. 在数据库操作中使用context

在进行数据库操作时,使用context可以确保长时间运行的查询在必要时能够被取消。

func getUserByID(ctx context.Context, db *sql.DB, id int) (*User, error) {var user Userquery := "SELECT id, name, email FROM users WHERE id = $1"row := db.QueryRowContext(ctx, query, id)err := row.Scan(&user.ID, &user.Name, &user.Email)if err != nil {return nil, err}return &user, nil
}

常见问题及解决方案

1. 忘记调用cancel函数

忘记调用cancel函数会导致资源泄漏。确保在每次创建context时都使用defer来调用cancel函数。

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
2. context中的值类型不一致

在使用context.Value时,确保传递和接收的值类型一致。可以通过定义常量或类型来避免类型错误。

type key intconst userKey key = 0func handler(w http.ResponseWriter, r *http.Request) {user := r.Context().Value(userKey)if user == nil {http.Error(w, "User not found", http.StatusUnauthorized)return}fmt.Fprintf(w, "Hello, %s!", user.(string))
}
3. context的传递深度过深

在复杂的系统中,context的传递深度可能会很深。为了避免代码复杂性,可以考虑使用中间件或封装函数来简化context的传递。

func withContext(ctx context.Context, fn func(context.Context) error) error {return fn(ctx)
}func main() {ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()if err := withContext(ctx, func(ctx context.Context) error {// 执行操作return nil}); err != nil {log.Println("Error:", err)}
}

进阶主题

1. 自定义context类型

在某些情况下,你可能需要自定义context类型以满足特定需求。可以通过继承context.Context接口来实现。

type customContext struct {context.ContextcustomData string
}func NewCustomContext(parent context.Context, data string) context.Context {return &customContext{parent, data}
}func (c *customContext) CustomData() string {return c.customData
}func handler(w http.ResponseWriter, r *http.Request) {ctx := NewCustomContext(r.Context(), "some custom data")// 使用自定义上下文fmt.Fprintf(w, "Custom data: %s", ctx.(*customContext).CustomData())
}
2. context的性能优化

在高并发场景下,频繁创建和销毁context可能会带来性能开销。可以通过复用context或使用池化技术来优化性能。

var contextPool = sync.Pool{New: func() interface{} {return context.WithValue(context.Background(), "key", "value")},
}func handler(w http.ResponseWriter, r *http.Request) {ctx := contextPool.Get().(context.Context)defer contextPool.Put(ctx)// 使用复用的上下文fmt.Fprintf(w, "Using pooled context")
}

总结

通过上述内容,我们进一步探讨了context在实际项目中的最佳实践、常见问题及解决方案,以及一些进阶主题。希望这些内容能帮助你在实际开发中更好地利用context,提高代码的健壮性和可维护性。希望这些详细的解释和示例能帮助你更好地理解和使用context

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

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

相关文章

MATLAB实现GARCH(广义自回归条件异方差)模型计算VaR(Value at Risk)

MATLAB实现GARCH(广义自回归条件异方差)模型计算VaR(Value at Risk) 1.计算模型介绍 使用GARCH&#xff08;广义自回归条件异方差&#xff09;模型计算VaR&#xff08;风险价值&#xff09;时&#xff0c;方差法是一个常用的方法。GARCH模型能够捕捉到金融时间序列数据中的波…

基于YOLOv8深度学习的智慧课堂学生专注度检测系统(PyQt5界面+数据集+训练代码)

本研究提出了一种基于YOLOv8深度学习的智慧课堂学生专注度检测系统&#xff0c;旨在实现对课堂中学生专注度的实时分析与评估。随着智慧教育的快速发展&#xff0c;学生的课堂表现和专注度成为评估学习效果的重要因素之一。然而&#xff0c;传统的专注度评估方法往往依赖于主观…

如何在 Ubuntu 上安装 Emby 媒体服务器

Emby 是一个开源的媒体服务器解决方案&#xff0c;它能让你整理、流媒体播放和分享你的个人媒体收藏&#xff0c;包括电影、音乐、电视节目和照片。Emby 帮你集中多媒体内容&#xff0c;让你无论在家还是在外都能轻松访问。它还支持转码&#xff0c;让你能够播放各种格式的内容…

HarmonyOS 如何获取设备信息(系统、版本、网络连接状态)

文章目录 前言一、引入模块和基本设备信息的获取二、设备硬件和系统版本信息的获取三、获取安全相关的设备信息四、获取网络状态信息五、完整 Demo 代码1. 导入所需模块2. 获取设备基本信息代码解析 3. 检测网络连接状态4. 执行函数 总结 前言 HarmonyOS 提供了一个强大的 API…

ES6笔记

ES6 ECMAScript ECMA组织&#xff1a;脚本语言标准化国际组织 1.什么是ES6 ES的全称是&#xff1a;ECMAScript&#xff0c;它是ECMA国际标准化组织制定的一项脚本语言的标准规范 2015年6月&#xff1a;ES2015 2016年6月&#xff1a;ES2016 2017年6月&#xff1a;ES2017 2018年…

常用命令之LinuxOracleHivePython

1. 用户改密 passwd app_adm chage -l app_adm passwd -x 90 app_adm -> 执行操作后&#xff0c;app_adm用户的密码时间改为90天有效期--查看该euser用户过期信息使用chage命令 --chage的参数包括 ---m 密码可更改的最小天数。为零时代表任何时候都可以更改密码。 ---M 密码…

游戏如何应对内存修改

据观察&#xff0c;近年来游戏黑灰产攻击角度多样化趋势显著&#xff0c;主要面临工作室、定制注入挂、模拟点击挂、内存修改挂、破解版等多方面安全问题。 据FairGuard数据统计&#xff0c;在游戏面临的众多安全风险中&#xff0c;「内存修改」攻击占比约为13%&#xff0c;主…

STM32单片机设计防儿童人员误锁/滞留车内警报系统

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 近年来在车辆逐渐普及的情况下&#xff0c;由于家长的疏忽&#xff0c;将…

华为欧拉系统使用U盘制作引导安装华为欧拉操作系统

今天记录一下通过U盘来安装华为欧拉操作系统 华为欧拉操作系统是国产的一个类似于Centos的Linus系统 具体实现操作步骤&#xff1a; 先在官网下载欧拉系统镜像点击跳转到下载 准备好一个大于16g的U盘 &#xff0c;用于制作U盘启动 下载一个引导程序制作工具&#xff0c;我使用…

Excel单元格中自适应填充多图

实例需求&#xff1a;在Excel插入图片时&#xff0c;由于图片尺寸各不相同&#xff0c;如果希望多个图片填充指定单元格&#xff0c;依靠用户手工调整&#xff0c;不仅费时费力&#xff0c;而且很难实现完全填充。如下图中的产品图册&#xff0c;有三个图片&#xff0c;如下图所…

51单片机应用开发---LCD1602显示应用

实现目标 1、了解LCD1602液晶屏&#xff1b; 2、掌握驱动程序的编写&#xff1b; 3. 具体目标&#xff1a;在屏幕上显示字符。 一、LCD1206概述 1.1 定义 LCD1602(Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块,可以显示ASCII码的标准字符和其它的一些内置…

问题分析与解决:Android开机卡动画问题分析

1. 问题背景及描述 在一个android设备的开发的项目中遇到了一个比较典型的问题:在主板贴片完成后,首次刷入androdi固件验证时,遇到了按键出发开机后,系统启动到android动画界阶段时一直循环卡在此阶段,无法进入桌面。如下如所示: 此问题在许多android项目的首次点亮阶段均…

nfs服务器--RHCE

一&#xff0c;简介 NFS&#xff08;Network File System&#xff0c;网络文件系统&#xff09;是FreeBSD支持的文件系统中的一种&#xff0c;它允许网络中的计 算机&#xff08;不同的计算机、不同的操作系统&#xff09;之间通过TCP/IP网络共享资源&#xff0c;主要在unix系…

黑马智慧商城项目学习笔记

目录 智慧商城项目创建项目调整初始化目录vant组件库vant按需导入和全部导入 项目中的vw适配路由设计配置登录页静态布局图形验证码功能request模块-axios封装api模块-封装图片验证码接口 Toast轻提示&#xff08;vant组件&#xff09;短信验证倒计时功能登录功能响应拦截器统一…

MySQL中将一个字符串字段按层级树状展开

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 需求1.分析2.实现3.思路刨析表结构和数据 需求 数据库中有个字段如下 如何将其转换为如下形式&#xff1a; 1.分析 1.他的层级个数是不确定的&#xff0c;也就是说有的有2层有的有5…

IDEA优雅debug

目录 引言一、断点分类&#x1f384;1.1 行断点1.2 方法断点1.3 属性断点1.4 异常断点1.5 条件断点1.6 源断点1.7 多线程断点1.8 Stream断点 二、调试动作✨三、Debug高级技巧&#x1f389;3.1 watch3.2 设置变量3.3 异常抛出3.4 监控JVM堆大小3.5 数组过滤和筛选 引言 使用ID…

springboot基于Web足球青训俱乐部管理后台系统开发(代码+数据库+LW)

摘 要 随着社会经济的快速发展&#xff0c;人们对足球俱乐部的需求日益增加&#xff0c;加快了足球健身俱乐部的发展&#xff0c;足球俱乐部管理工作日益繁忙&#xff0c;传统的管理方式已经无法满足足球俱乐部管理需求&#xff0c;因此&#xff0c;为了提高足球俱乐部管理效率…

STM32保护内部FLASH

在实际发布的产品中&#xff0c;在STM32芯片的内部FLASH存储了控制程序&#xff0c;如果不作任何保护措施的话&#xff0c;可以使用下载器直接把内部FLASH的内容读取回来&#xff0c;得到bin或hex文件格式的代码拷贝&#xff0c;别有用心的厂商即可利用该代码文件山寨产品。为此…

树的直径计算:算法详解与实现

树的直径计算:算法详解与实现 1. 引言2. 算法概述3. 伪代码实现4. C语言实现5. 算法分析6. 结论在图论中,树的直径是一个关键概念,它表示树中任意两点间最长路径的长度。对于给定的树T=(V,E),其中V是顶点集,E是边集,树的直径定义为所有顶点对(u,v)之间最短路径的最大值。…

无人机场景 - 目标检测数据集 - 车辆检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;无人机场景车辆检测数据集&#xff0c;真实场景高质量图片数据&#xff0c;涉及场景丰富&#xff0c;比如无人机场景城市道路行驶车辆图片、无人机场景城市道边停车车辆图片、无人机场景停车场车辆图片、无人机场景小区车辆图片、无人机场景车辆遮挡、车…