第 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.xdnf.cn/news/18350.html

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

相关文章

计算机毕业设计 | springboot+vue大学城水电管理系统 校园学校物业水电管理(附源码+文档)

1&#xff0c;绪论 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理大学城水电管理系统的相关信息成…

5-对象的访问权限

对象的访问权限知识点 对象的分类 在数据库中&#xff0c;数据库的表、索引、视图、缺省值、规则、触发器等等、都可以被称为数据库对象&#xff0c;其中对象主要分为两类 1、模式(schema)对象&#xff1a;模式对象可以理解为一个存储目录、包含视图、索引、数据类型、函数和…

药方新解:Spring Boot中药实验管理系统设计

3系统分析 3.1可行性分析 通过对本中药实验管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本中药实验管理系统采用SSM框架&#xff0c;JAVA作为开发语…

动态规划-完全背包问题——279.完全平方数

1.题目解析 题目来源 279.完全平方数——力扣 测试用例 2.算法原理 1.状态表示 完全背包问题通常都是使用一个二维数组来表示其状态&#xff0c;这里是 dp[i][j]&#xff1a;在[1,i]区间选择平方数&#xff0c;当此时已选择平方数的总和完全等于j时所选择的最小平方数个数 …

二叉树的层序遍历

一、题目 给定一个二叉树&#xff0c;返回该二叉树层序遍历的结果&#xff0c;&#xff08;从左到右&#xff0c;一层一层地遍历&#xff09; 例如&#xff1a; 给定的二叉树是{3,9,20,null,null,15,7}, 该二叉树层序遍历的结果是 [[3],[9,20],[15,7]] 二、解决方案 2.0 树…

模型训练过程的显存占用实测

依赖项说明 pip install nvitop pip install timm pip install peft后续的显存占用数据截图&#xff0c;均基于nvitop命令实现 1、模型显存占用说明 1.1 理论占用值 在 一文讲明白大模型显存占用&#xff08;只考虑单卡&#xff09;与大模型显存占用分析都对模型训练过程中…

后端分层解耦

引入 在上篇所举的例子中&#xff0c;我们将所有的代码均放在HelloControl方法之中&#xff0c;这样会导致代码的复用性、可读性较差&#xff0c;难以维护。因此我们需 三层架构 在之前的代码中&#xff0c;代码大体可以分为三部分&#xff1a;数据访问、数据逻辑处理、响应数…

AIGC 入门全攻略:开启智能创作新时代

一、AIGC 初印象 AIGC&#xff0c;即人工智能生成内容&#xff0c;是继专业生产内容&#xff08;PGC&#xff09;、用户生产内容&#xff08;UGC&#xff09;之后的新型内容创作方式。它涵盖了文本生成、图像与视频创作、音频生成等多个领域&#xff0c;正在以惊人的速度改变着…

约克VRF地暖中央空调,让你舒适过冬

想要冬季过得舒服&#xff0c;采暖必须要到位&#xff01;对于没有集中供暖的南方地区来说&#xff0c;冬季室内阴冷刺骨。 选购地暖中央空调时&#xff0c;强效制热的能力必不可少&#xff0c;让我们可以享受温暖的室内温度&#xff0c;有效减少室内忽冷忽热的温度变化。 约克…

基于Java Springboot宠物领养救助平台

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

使用原生 OpenTelemetry 解锁各种可能性:优先考虑可靠性,而不是专有限制

作者&#xff1a;来自 Elastic Bahubali Shetti•Miguel Luna Elastic 现在支持使用 OTel Operator 在 Kubernetes 上部署和管理 Elastic Distributions of OpenTelemetry (EDOT)。SRE 现在可以访问开箱即用的配置和仪表板&#xff0c;这些配置和仪表板旨在通过 Elastic Observ…

基于python Django的boss直聘数据采集与分析预测系统,爬虫可以在线采集,实时动态显示爬取数据,预测基于技能匹配的预测模型

本系统是基于Python Django框架构建的“Boss直聘”数据采集与分析预测系统&#xff0c;旨在通过技能匹配的方式对招聘信息进行分析与预测&#xff0c;帮助求职者根据自身技能找到最合适的职位&#xff0c;同时为招聘方提供更精准的候选人推荐。系统的核心预测模型基于职位需求技…

安装 python-pcl 遇到的问题

安装python-pcl 成功安装错误尝试尝试一尝试二尝试三 本人环境 Ubuntu 22.04.4LTS ros2-humble cpython 3.0.11 python 3.10.12 libpcl-dev 1.12.1dfsg-3build1 pcl-tools 1.12.1dfsg-3build1 代码摘抄来源&#xff1a;Breadcrumbsouster-ros-extras/scripts/ros2_pcl_filters.…

【C++进阶篇】——string类的使用

文章目录 前言&#xff1a;1. string的介绍2. string类对象的常见构造3. string类对象的容量操作4. string类对象的访问5. 迭代器6. string类对象的修改操作7. string类对象的字符串运算8.string类成员函数9.string类非成员函数10.string类常量成员 前言&#xff1a; std::str…

vmware虚拟机给创建的centos扩展磁盘步骤

1.先看看原来的磁盘信息&#xff0c;目前磁盘是20g的&#xff0c;重点关注红色箭头指向的地方&#xff0c;一个17g 可用11g&#xff0c;接下来要对其进行扩展 df -h2.关闭当前虚拟机&#xff0c;先进行磁盘扩展&#xff0c;目前我扩展到了50g。 3.重新开启虚拟机&#xff0c;…

开源物业管理系统助力智能社区提升服务效率与用户体验

内容概要 开源物业管理系统是一种灵活、智能的解决方案&#xff0c;专为社区物业管理而生。随着智能社区的发展&#xff0c;这种系统变得越来越重要。它不仅帮助物业管理者高效地处理日常事务&#xff0c;还提升了居民的生活体验。在这个日新月异的时代&#xff0c;开源物业管…

深入理解 Redis跳跃表 Skip List 原理|图解查询、插入

1. 简介 跳跃表 ( skip list ) 是一种有序数据结构&#xff0c;通过在每个节点中维持多个指向其他节点的指针&#xff0c;从而达到快速访问节点的目的。 在 Redis 中&#xff0c;跳跃表是有序集合键的底层实现之一&#xff0c;那么这篇文章我们就来讲讲跳跃表的实现原理。 2. …

【数据库】mysql数据库迁移前应如何备份数据?

MySQL 数据库的备份是确保数据安全的重要措施之一。在进行数据库迁移之前&#xff0c;备份现有数据可以防止数据丢失或损坏。以下是一套详细的 MySQL 数据库备份步骤&#xff0c;适用于大多数情况。请注意&#xff0c;具体的命令和工具可能因 MySQL 版本的不同而有所差异。整个…

AWTK-WIDGET-WEB-VIEW 实现笔记 (4) - Ubuntu

Ubuntu 上实现 AWTK-WIDGET-WEB-VIEW 开始以为很简单&#xff0c;后来发现是最麻烦的。因为 Ubuntu 上的 webview 库是 基于 GTK 的&#xff0c;而 AWTK 是基于 X11 的&#xff0c;两者的窗口系统不同&#xff0c;所以期间踩了几个大坑。 1. 编译 AWTK 在使用 Linux 的输入法时…

Rocket入门练习

搭建部署&#xff1a; 1. 部署平台和部署方式&#xff1a; Ubuntu&#xff1a;22.10 部署方式&#xff1a;源码安装部署 a. 下载源码到本地&#xff1a;rocketmq-all-5.3.1-source-release.zip $ unzip rocketmq-all-5.3.1-source-release.zip // 解压缩 $ cd rocketmq-all…