第 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()
返回一个时间点,表示这个请求的截止时间。如果返回的ok
为false
,则没有设置截止时间。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
接口的结构体,如cancelCtx
、timerCtx
等。以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
,例如同时设置超时和取消。这可以通过嵌套调用WithCancel
和WithTimeout
来实现。
示例代码:
parentCtx := context.Background()
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel()ctx, cancel = context.WithCancel(ctx)
defer cancel()
最佳实践
-
避免过度使用
Value
:虽然Value
方法非常方便,但过度使用可能会导致代码难以维护。应该只传递真正需要的信息。 -
及时释放资源:使用
defer
确保cancel
函数总是被调用,这样可以避免资源泄露。 -
不要在
context
中存储大量数据:context
中的数据应该是轻量级的,避免存储大对象。 -
避免直接使用
context.Background
或context.TODO
:在实际应用中,应该根据具体需求创建适当的context
。
常见误区
-
误用
context.TODO
:context.TODO
是一个占位符,用于表示将来会提供一个合适的context
。在生产代码中,应该使用具体的context
,而不是TODO
。 -
忽略
Done
通道:在处理长时间运行的任务时,应该始终监听Done
通道,以便在接收到取消信号时能够及时停止任务。 -
错误地传递
context
:context
应该从请求的入口点传递到所有需要它的组件,而不仅仅是部分组件。
源码深入
让我们更深入地看看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. 统一处理取消和超时
在实际项目中,经常会遇到需要同时处理取消和超时的情况。可以通过组合WithTimeout
和WithCancel
来实现这一点。
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
。
相关文章:
第 17 章 - Go语言 上下文( Context )
在Go语言中,context包为跨API和进程边界传播截止时间、取消信号和其他请求范围值提供了一种方式。它主要应用于网络服务器和长时间运行的后台任务中,用于控制一组goroutine的生命周期。下面我们将详细介绍context的定义、使用场景、取消和超时机制&#…...

Android Framework AMS(16)进程管理
该系列文章总纲链接:专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明: 说明:本章节主要解读AMS 进程方面的知识。关注思维导图中左上侧部分即可。 我们本章节主要是对Android进程管理相关知识有一个基本的了解。先来了解下L…...

STM32设计防丢防摔智能行李箱
目录 目录 前言 一、本设计主要实现哪些很“开门”功能? 二、电路设计原理图 1.电路图采用Altium Designer进行设计: 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着科技的不断发展,嵌入式系统、物联网技术、智能设备…...

【异常解决】Linux shell报错:-bash: [: ==: 期待一元表达式 解决方法
博主介绍:✌全网粉丝21W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

ML 系列: 第 23 节 — 离散概率分布 (多项式分布)
目录 一、说明 二、多项式分布公式 2.1 多项式分布的解释 2.2 示例 2.3 特殊情况:二项分布 2.4 期望值 (Mean) 2.5 方差 三、总结 3.1 python示例 一、说明 伯努利分布对这样一种情况进行建模:随机变量可以采用两个可能的值&#…...
Webpack 1.13.2 执行 shell 命令解决 打印时没有背景色和文字颜色的问题
这是因为 Webpack 1.13.2 不支持新的插件钩子 API。Webpack 1 的插件系统使用的是 plugin 方法,而不是 Webpack 4 中的 hooks。 在 Webpack 1 中,你可以使用以下代码来确保 sed 命令在打包完成后执行: const { exec } require(child_proce…...

C++构造函数详解
构造函数详解:C 中对象初始化与构造函数的使用 在 C 中,构造函数是一种特殊的成员函数,它在创建对象时自动调用,用来初始化对象的状态。构造函数帮助我们确保每个对象在被创建时就处于一个有效的状态,并且在不传递任何…...

POI实现根据PPTX模板渲染PPT
目录 1、前言 2、了解pptx文件结构 3、POI组件 3.1、引入依赖 3.2、常见的类 3.3、实现原理 3.4、关键代码片段 3.4.1、获取ppt实例 3.4.2、获取每页幻灯片 3.4.3、循环遍历幻灯片处理 3.4.3.1、文本 3.4.3.2、饼图 3.4.3.3、柱状图 3.4.3.4、表格 3.4.3.5、本地…...

【论文模型复现】深度学习、地质流体识别、交叉学科融合?什么情况,让我们来看看
文献:蓝茜茜,张逸伦,康志宏.基于深度学习的复杂储层流体性质测井识别——以车排子油田某井区为例[J].科学技术与工程,2020,20(29):11923-11930. 本文目录 一、前言二、文献阅读-基于深度学习的复杂储层流体性质测井识别2.1 摘要2.2 当前研究不足2.3 本文创新2.4 论文…...

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

conda创建 、查看、 激活、删除 python 虚拟环境
1、创建 python 虚拟环境 ,假设该环境命名为 “name”。 conda create -n name python3.11 2、查看 python 虚拟环境。 conda info -e 3、激活使用 python 虚拟环境。 conda activate name 4、删除 python 虚拟环境 conda remove -n name --all 助力快速掌握数据集…...

vs2022搭建opencv开发环境
1 下载OpenCV库 https://opencv.org/ 下载对应版本然后进行安装 将bin目录添加到系统环境变量opencv\build\x64\vc16\bin 复制该路径 打开高级设置添加环境变量 vs2022新建一个空项目 修改属性添加头文件路径和库路径 修改链接器,将OpenCV中lib库里的o…...

NVIDIA NIM 开发者指南:入门
NVIDIA NIM 开发者指南:入门 NVIDIA 开发者计划 想要了解有关 NIM 的更多信息?加入 NVIDIA 开发者计划,即可免费访问任何基础设施云、数据中心或个人工作站上最多 16 个 GPU 上的自托管 NVIDIA NIM 和微服务。 加入免费的 NVIDIA 开发者计…...

探索Python网络请求新纪元:httpx库的崛起
文章目录 **探索Python网络请求新纪元:httpx库的崛起**第一部分:背景介绍第二部分:httpx库是什么?第三部分:如何安装httpx库?第四部分:简单的库函数使用方法1. 发送GET请求2. 发送POST请求3. 超…...
学了Arcgis的水文分析——捕捉倾泻点,河流提取与河网分级,3D图层转要素失败的解决方法,测量学综合实习网站存着
ArcGIS水文分析实战教程(7)细说流域提取_汇流域栅格-CSDN博客 ArcGIS水文分析实战教程(6)河流提取与河网分级_arcgis的dem河流分级-CSDN博客 ArcGIS水文分析实战教程(5)细说流向与流量-CSDN博客 ArcGIS …...

QQ 小程序已发布,但无法被搜索的解决方案
前言 我的 QQ 小程序在 2024 年 8 月就已经审核通过,上架后却一直无法被搜索到。打开后,再在 QQ 上下拉查看 “最近使用”,发现他出现一下又马上消失。 上线是按正常流程走的,开发、备案、审核,没有任何违规…...

【C++】拷贝构造 和 赋值运算符重载
目录: 一、拷贝构造 (一)拷贝函数的特点 二、赋值运算符重载 (一)运算符重载 (二)赋值运算符重载 正文 一、拷贝构造 如果一个构造函数的第一个参数是自身类类型的引用,且任何…...

21.UE5游戏存档,读档,函数库
2-23 游戏存档、读档、函数库_哔哩哔哩_bilibili 目录 1.存档蓝图 2.函数库 2.1保存存档 2.2读取存档: 3.加载游戏,保存游戏 3.1游戏实例对象 3.2 加载游戏 3.3保存游戏 这一节的内容较为错综复杂,中间没有运行程序进行阶段性成果的验…...
「Mac玩转仓颉内测版14」PTA刷题篇5 - L1-005 考试座位号
本篇将继续讲解PTA平台上的题目 L1-005 考试座位号,通过考生准考证号与座位号的对应关系,掌握简单的数据查询与映射操作,进一步提升Cangjie编程语言的实际应用能力。 关键词 PTA刷题数据查询映射操作输入输出Cangjie语言 一、L1-005 考试座位…...
Vue3引用高德地图,进行位置标记获取标记信息
首先安装地图插件 cnpm i amap/amap-jsapi-loader --save封装地图子组件 <template><el-dialogtitle"选择地点"width"740px"class"select-map-dialog"v-model"dialogShow":close-on-click-modal"false":modal-or…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...