第 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…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...

【C++】纯虚函数类外可以写实现吗?
1. 答案 先说答案,可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...

ubuntu22.04有线网络无法连接,图标也没了
今天突然无法有线网络无法连接任何设备,并且图标都没了 错误案例 往上一顿搜索,试了很多博客都不行,比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动,重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
FOPLP vs CoWoS
以下是 FOPLP(Fan-out panel-level packaging 扇出型面板级封装)与 CoWoS(Chip on Wafer on Substrate)两种先进封装技术的详细对比分析,涵盖技术原理、性能、成本、应用场景及市场趋势等维度: 一、技术原…...
前端工具库lodash与lodash-es区别详解
lodash 和 lodash-es 是同一工具库的两个不同版本,核心功能完全一致,主要区别在于模块化格式和优化方式,适合不同的开发环境。以下是详细对比: 1. 模块化格式 lodash 使用 CommonJS 模块格式(require/module.exports&a…...
背包问题双雄:01 背包与完全背包详解(Java 实现)
一、背包问题概述 背包问题是动态规划领域的经典问题,其核心在于如何在有限容量的背包中选择物品,使得总价值最大化。根据物品选择规则的不同,主要分为两类: 01 背包:每件物品最多选 1 次(选或不选&#…...

aurora与pcie的数据高速传输
设备:zynq7100; 开发环境:window; vivado版本:2021.1; 引言 之前在前面两章已经介绍了aurora读写DDR,xdma读写ddr实验。这次我们做一个大工程,pc通过pcie传输给fpga,fpga再通过aur…...

JUC并发编程(二)Monitor/自旋/轻量级/锁膨胀/wait/notify/锁消除
目录 一 基础 1 概念 2 卖票问题 3 转账问题 二 锁机制与优化策略 0 Monitor 1 轻量级锁 2 锁膨胀 3 自旋 4 偏向锁 5 锁消除 6 wait /notify 7 sleep与wait的对比 8 join原理 一 基础 1 概念 临界区 一段代码块内如果存在对共享资源的多线程读写操作…...