Golang 中的 Context 包
简介
今天,我们将讨论 Go 编程中非常重要的一个主题:context
包。如果你现在觉得它很令人困惑,不用担心 — 在本文结束时,你将像专家一样处理 context!
想象一下,你在一个主题公园,兴奋地准备搭乘一座巨大的过山车。但有个问题:排队的人非常多,而且公园快要关门,你只有一个小时的时间。你会怎么办?嗯,你可能会等一会儿,但不会等一个小时,对吧?如果你等了 30 分钟还没有到前面,你会离开队伍去尝试其他游乐设施。这就是我们所谓的 ‘超时’。
现在,想象一下,你还在排队,突然下起了倾盆大雨。过山车的操作员决定关闭过山车。你不会继续排队等待根本不会发生的事情,对吧?你会立刻离开队伍。这就是我们所谓的 ‘取消’。
在编程世界中,我们经常面临类似的情况。我们要求程序执行可能需要很长时间或需要因某种原因停止的任务。这就是 context
包发挥作用的地方。它允许我们优雅地处理这些超时和取消。
它是如何工作的
**创建上下文:**我们首先创建一个上下文。这就像排队等待过山车一样。
ctx := context.Background() // This gives you an empty context
**设置超时:**接下来,我们可以在上下文中设置超时。这就好比你决定在排队多久后放弃并去尝试其他游乐设施。
ctxWithTimeout, cancel := context.WithTimeout(ctx, time.Second*10) // Wait for 10 seconds
// Don't forget to call cancel when you're done, or else you might leak resources!
defer cancel()
**检查超时:**现在,我们可以使用上下文来检查是否等待时间太长,是否应该停止我们的任务。这就好比在排队等待时看看手表。
select {
case <-time.After(time.Second * 15): // This task takes 15 secondsfmt.Println("Finished the task")
case <-ctxWithTimeout.Done():fmt.Println("We've waited too long, let's move on!") // We only wait for 10 seconds
}
**取消上下文:**最后,如果出于某种原因需要停止任务,我们可以取消上下文。这就好比听到因下雨而宣布过山车关闭。
cancel() // We call the cancel function we got when we created our context with timeout
示例 1:慢速数据库查询
想象一下构建一个从数据库中获取用户数据的Web应用程序。有时,数据库响应较慢,你不希望用户永远等下去。在这种情况下,你可以使用带有超时的上下文。
func getUser(ctx context.Context, id int) (*User, error) {// Create a new context that will be cancelled if it takes more than 3 secondsctx, cancel := context.WithTimeout(ctx, 3*time.Second)defer cancel()// Assume db.QueryRowContext is a function that executes a SQL query and returns a rowrow := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = ?", id)var name stringif err := row.Scan(&name); err != nil {return nil, err}return &User{Name: name}, nil
}
在这个示例中,如果数据库查询花费超过3秒的时间,上下文将被取消,db.QueryRowContext
应返回一个错误。
示例 2:网页抓取
假设你正在编写一个用于从网站抓取数据的程序。然而,该网站有时响应较慢,或者根本不响应。你可以使用上下文来防止你的程序陷入困境。
func scrapeWebsite(ctx context.Context, url string) (*html.Node, error) {// Create a new context that will be cancelled if it takes more than 5 secondsctx, cancel := context.WithTimeout(ctx, 5*time.Second)defer cancel()// Create a request with the contextreq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)if err != nil {return nil, err}// Execute the requestresp, err := http.DefaultClient.Do(req)if err != nil {return nil, err}defer resp.Body.Close()// Parse the response body as HTMLreturn html.Parse(resp.Body), nill
}
在这个示例中,如果从网站获取数据超过5秒,上下文将被取消,http.DefaultClient.Do
应该返回一个错误。
示例3:长时间运行的任务
假设你有一个执行长时间运行任务的程序,但你希望能够在程序接收到关闭信号时停止任务。这在一个 Web 服务器中可能会很有用,当关闭时必须停止提供请求并进行清理。
func doTask(ctx context.Context) {for {select {case <-time.After(1 * time.Second):// The task is done, we're ready to exitfmt.Println("Task is done")returncase <-ctx.Done():// The context was cancelled from the outside, clean up and exitfmt.Println("Got cancel signal, cleaning up")return}}
}func main() {// Create a new contextctx, cancel := context.WithCancel(context.Background())// Start the task in a goroutinego doTask(ctx)// Wait for a shutdown signal<-getShutdownSignal()// Cancel the context, which will stop the taskcancel()// Wait for a bit to allow the task to clean uptime.Sleep(1 * time.Second)
}
在这个示例中,当程序接收到关闭信号时,它会取消上下文,这会导致 doTask
在 <-ctx.Done()
上接收到信号。
示例4:HTTP 服务器
假设你正在构建一个处理传入请求的 HTTP 服务器。一些请求可能需要很长时间来处理,你希望设置一个最长处理时间限制。
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)defer cancel()// Simulate a long-running operationselect {case <-time.After(3 * time.Second):w.Write([]byte("Operation finished."))case <-ctx.Done():w.Write([]byte("Operation timed out."))}
})http.ListenAndServe(":8080", nil)
在这个示例中,如果操作需要超过2秒的时间,上下文将被取消,并且服务器将响应“操作超时”。
示例5:同步多个 Goroutines
假设你正在编写一个程序,使用 Goroutines 并发执行多个任务。如果其中一个任务失败,你希望取消所有其他任务。
func doTask(ctx context.Context, id int) {select {case <-time.After(time.Duration(rand.Intn(4)) * time.Second):fmt.Printf("Task %v finished.\n", id)case <-ctx.Done():fmt.Printf("Task %v cancelled.\n", id)}
}func main() {ctx, cancel := context.WithCancel(context.Background())for i := 1; i <= 5; i++ {go doTask(ctx, i)}// Cancel the context after 2 secondstime.Sleep(2 * time.Second)cancel()// Give the tasks some time to finish uptime.Sleep(1 * time.Second)
}
在这个示例中,当上下文被取消时,仍在运行的任何任务都将收到 <-ctx.Done()
,从而允许它们进行清理并退出。
仍然在尝试理解吗?
当我第一次接触上下文时,我感到非常困惑,我提出了一个问题,即如果 select 前面的命令花费太长时间,那么我们永远无法检测到 取消,这是一个合理的问题。因此,我准备了另一个示例来详细解释这种情况。
package mainimport ("context""fmt""math/rand""time"
)func expensiveCalculation(ctx context.Context, resultChan chan<- int) {// Simulate a long-running calculationrand.Seed(time.Now().UnixNano())sleepTime := time.Duration(rand.Intn(20)+1) * time.Secondfmt.Printf("Calculation will take %s to complete\n", sleepTime)time.Sleep(sleepTime)select {case <-ctx.Done():// Context was cancelled, don't write to the channelreturndefault:// Write the result to the channelresultChan <- 42 // replace with your actual calculation result}
}func main() {// Create a context that will be cancelled after 10 secondsctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)defer cancel() // The cancel should be deferred so resources are cleaned upresultChan := make(chan int)// Start the expensive calculation in a separate goroutinego expensiveCalculation(ctx, resultChan)// Wait for either the result or the context to be doneselect {case res := <-resultChan:// Got the resultfmt.Printf("Calculation completed with result: %d\n", res)case <-ctx.Done():// Context was cancelledfmt.Println("Calculation cancelled")}
}
time.Sleep(sleepTime)
命令是阻塞的,将暂停 goroutine 的执行,直到指定的持续时间已过。这意味着 select
语句不会被执行,直到休眠时间已经过去。
然而,上下文的取消与 goroutine 内的执行是独立的。如果上下文的截止时间被超过或其 cancel()
函数被调用,它的 Done()
通道将被关闭。
在主 goroutine 中,您有另一个 select
语句,它将立即检测上下文的 Done()
通道是否已关闭,并在不等待 expensiveCalculation
goroutine 完成休眠的情况下打印 “Calculation cancelled”。
也就是说,expensiveCalculation
goroutine 将在休眠后继续执行,它将在尝试写入 resultChan
之前检查上下文是否已被取消。如果已被取消,它将立即返回。这是为了避免潜在的死锁,如果没有其他goroutine从 resultChan
读取。
如果需要昂贵的计算(在本例中由 time.Sleep
模拟)在取消时立即停止,您必须设计计算以周期性地检查上下文是否已取消。这通常在需要将计算分解为较小部分的情况下使用循环。如果计算不能分解,并需要一次运行完毕,那么很遗憾,在 Go 中无法提前停止它。
相关文章:

Golang 中的 Context 包
简介 今天,我们将讨论 Go 编程中非常重要的一个主题:context 包。如果你现在觉得它很令人困惑,不用担心 — 在本文结束时,你将像专家一样处理 context! 想象一下,你在一个主题公园,兴奋地准备…...

nginx服务器
nginx反向代理 nginx 反向代理的好处: 提高访问速度 因为nginx本身可以进行缓存,如果访问的同一接口,并且做了数据缓存, nginx就直接可把数据返回,不需要真正地访问服务端,从而提高访问速度。 进行负载均衡…...
电脑常用快捷键
一、Win键(徽标键) 以下是Windows操作系统中使用Win键(徽标键)结合A-Z的所有快捷方式描述: - Win A:打开操作中心,访问系统通知、快速设置和其他功能 - Win B:将焦点设置到任务栏…...

吴恩达《机器学习》8-3->8-4:模型表示I、模型表示II
8.3、模型表示I 一、大脑神经网络的基本原理 为了构建神经网络模型,首先需要理解大脑中的神经网络是如何运作的。每个神经元都可以被看作是一个处理单元或神经核,它包含多个输入(树突)和一个输出(轴突)。…...

数据结构-二叉树力扣题
目录 1.相同的树 2.二叉树中查找值为x的节点 3.单值二叉树 4.对称二叉树 5.二叉树的前序遍历 6.另一颗树的子树 层序遍历: 7.二叉树遍历 8.判断二叉树是否是完全二叉树 一个特殊的性质: 1.相同的树 题目链接:力扣(LeetC…...

node 第十八天 中间件express-session实现会话密钥
express-session 文档 express-session 一个简单的express会话中间件 使用场景 在一个系统中, 需要维持一个临时的与登录态无关的会话密钥 比如登录系统后, 请求某一个接口, 接口的行为与登录态无关, 也就是说任何人对接口的访问…...

【机器学习基础】机器学习入门(1)
🚀个人主页:为梦而生~ 关注我一起学习吧! 💡专栏:机器学习 欢迎订阅!后面的内容会越来越有意思~ 💡专栏介绍: 本专栏的第一篇文章,当然要介绍一下了~来说一下这个专栏的开…...

赶快来!程序员接单必须知道的六大注意事项!!!
花花世界迷人眼,增加实力多搞钱!对于咱程序员来说,搞钱的最好办法就是网上接单了,相信也有不少小伙伴已经在尝试了吧!但是如何正确的搞钱呢?其中的注意事项你真的了解吗? 本期就和小编一起来看…...

【C++】日期类实现,与日期计算相关OJ题
文章目录 日期类的设计日期计算相关OJ题HJ73 计算日期到天数转换KY111 日期差值KY222 打印日期KY258 日期累加 在软件开发中,处理日期是一项常见的任务。为了方便地操作日期,我们可以使用C编程语言来创建一个简单的日期类。在本文中,我们将介…...

前端404页面的制作
1、背景 前端开发经常遇到输入路径不存在的问题,为此,把之前项目的404拿出来供大家参考。代码很简单,适合新手入手,效果如下: 2、代码引用的是element-plus框架 <template><div><el-result icon"…...

深兰科技轮腿家用AI机器人荣获“2023年度城市更新科创大奖”
近日,“2023金砖论坛第五季金立方城市更新科创大会”在上海举行,会上发布了《第12届金砖价值榜》,深兰科技研发出品的轮腿式家用AI机器人(兰宝),因其AI技术的创新性应用,荣获了“2023年度城市更新科创大奖”。 在10月2…...
669.修剪二叉树
原题链接:669.修剪二叉树 全代码: class Solution { public:TreeNode* trimBST(TreeNode* root, int low, int high) {if (root nullptr ) return nullptr;if (root->val < low) {TreeNode* right trimBST(root->right, low, high); // 寻找符合区间[l…...

论文绘图-机器学习100张模型图
在现代学术研究和技术展示中,高质量的图表和模型结构图是至关重要的。这尤其在机器学习领域更为显著,一个领域以其复杂的算法和复杂的数据结构而闻名。机器学习是一种使用统计技术使计算机系统能够从数据中学习和改进其任务执行的方法,而有效…...

PHP项目学习笔记-萤火商城-增加一个模块(表涉及到的操作和文件)
背景 是在store的后台添加一个页面,显示的如满意度调查的页面 在router.config.js里面配置一个新的菜单 路径:yoshop2.0-store\src\config\router.config.js 代码如下,很简单,定义了这菜单点击的时候进入的页面,和下面…...
如何用Java设计自动售货机?
如何用Java设计自动售货机?是大多在高级Java开发人员面试中经常被问到的好问题之一。在典型的编码面试中,你会得到一个问题描述来开发一个售货机,在有限的时间内,通常2到3小时内,你需要在Java中编写设计文档、工作代码和单元测试。这种Java面试的一个关键优势是可以一次测试候…...

JAVA数据代码示例
首先,我们需要导入一些必要的Java库 java import java.net.URL; import java.net.HttpURLConnection; import java.io.BufferedReader; import java.io.InputStreamReader; 然后,我们可以创建一个URL对象,表示我们要爬取的网页的URL。 jav…...
github常用搜索指令
一、常用搜索指令 以下指令可分开用,也可组合使用 根据关键字搜索 in:name xx继上一步:指定开发语言 language:Java in:name XX language:Java继上一步,指定更新日期 pushed:>2022-06-06 in:name XX language:Java pushed:>2022-0…...

为什么esp8266刷入了固件,无法接受AT指令
我遇到的解决方法是:是串口调试助手出了问题。所以需要更换一个串口调试助手软件。 上面这个就是我换了的软件 在开发的时候,经常会遇到软件故障,导致正确的方法,但是没有效果,好比以前用盗版的8.7版本的Proteus模拟…...
Scala---字符串、集合
一、字符串 StringStringBuilder 可变string操作方法举例 比较:equals比较忽略大小写:equalsIgnoreCaseindexOf:如果字符串中有传入的assci码对应的值,返回下标 1./** 2.* String && StringBuilder 3.*/ 4.val str "abcd" 5.val s…...

Power Automate-当收到HTTP请求时触发流程
选择创建自动化云端流,点跳过 第一个操作搜索HTTP,点击当收到HTTP请求时 点击使用示例有效负载生成架构 写入JSON,点击完成 正文JSON架构就自动生成了,再点击左下角的显示高级选项 Method根据需求选择 可以选择JSON中的参数赋值给…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...