Golang 中的 Context 包

img

简介

今天,我们将讨论 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 中无法提前停止它。

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

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

相关文章

界面控件DevExtreme图表和仪表(v23.1) - 新功能(Angular,React,Vue,jQuery)

本文将为大家总结下DevExtreme在v23.1版本中发布的一些与图表和仪表盘相关的功能。 DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#…

2023年【汽车驾驶员(中级)】免费试题及汽车驾驶员(中级)考试试卷

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【汽车驾驶员&#xff08;中级&#xff09;】免费试题及汽车驾驶员&#xff08;中级&#xff09;考试试卷&#xff0c;包含汽车驾驶员&#xff08;中级&#xff09;免费试题答案和解析及汽车驾驶员&#xff08;…

文心一言 VS 讯飞星火 VS chatgpt (135)-- 算法导论11.3 1题

一、用go语言&#xff0c;假设我们希望查找一个长度为 n 的链表&#xff0c;其中每一个元素都包含一个关键字k并具有散列值h(k)。每一个关键字都是长字符串。那么在表中查找具有给定关键字的元素时&#xff0c;如何利用各元素的散列值呢? 文心一言&#xff1a; 在Go语言中&a…

【JAVA学习笔记】70 - 反射

项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter23/src 反射 一、反射的引出 package com.yinhai.reflection.question;import com.yinhai.Cat;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IO…

jetsonTX2 nx配置tensorRT加速yolov5推理

环境说明 Ubuntu 18conda环境python3.9cuda10.2&#xff0c;硬件平台是Jetson tx2 nx 前提你已经能运行YOLOV5代码后&#xff0c;再配置tensorRT进行加速。 目前只试了图片检测和C打开USB摄像头进行视频检测&#xff0c;希望是使用python配合D435i深度相机来实现检测&#xff…

Qt 自定义全局悬浮置顶按钮

Qt 自定义全局悬浮置顶按钮 效果 使用示例 ResetButton* resetBtn; resetBtn new ResetButton(); resetBtn->show(); resetBtn->move(QPoint(1000, 800)); connect(resetBtn, &ResetButton::clicked, this, &MainWindow::Reset);自行加入图片&#xff0c;透明…

【QT】飞机大战

0 项目简介 飞机大战是我们大家所熟知的一款小游戏&#xff0c;本教程就是教大家如何制作一款自己的飞机大战 首先我们看一下效果图 玩家控制一架小飞机&#xff0c;然后自动发射子弹&#xff0c;如果子弹打到了飞下来的敌机&#xff0c;则射杀敌机&#xff0c;并且有爆炸的特…

Unity 调用自己封装好的DLL库

因为做项目时会用到很多重复的方法&#xff0c;每次都重新写有点浪费时间&#xff0c;就可以将这些方法封装成DLL类库&#xff0c;用的时候直接引入调用就行。 首先在VS里面创建类库文件 注&#xff1a;.NET Framework要选3.5以下 然后定义好命名空间名字和类名就可以写自己要…

在 Vue3 中使用 mitt 进行组件通信

npm 包地址 mitt 是一个轻量级的 JavaScript 事件触发器&#xff0c; 只有200b。有基本的事件触发、订阅和取消订阅功能&#xff0c;还支持用命名空间来进行更高级的事件处理。 功能特点&#xff1a; Microscopic —— weighs less than 200 bytes gzippedUseful —— a wil…

elemenui的Upload上传整合成数组对象

1. 普通直接上传 <el-upload action"" :before-upload"doBeforeUpload"><el-button type"success" size"mini">导入</el-button></el-upload> methods:{doBeforeUpload(file) {let reader new FileReader(…

mysql的主从复制,读写分离

主从复制&#xff1a;主mysql的数据&#xff0c;新增&#xff0c;修改&#xff0c;表里的数据都会同步到从mysql上 主从复制的模式&#xff1a; 1 异步复制 mysql 的最常用的复制&#xff0c;只要执行完&#xff0c;客户端提交事务&#xff0c;主mysql 会立即把结果返回给从…

gdb详解【Linux知识贩卖机】

你背朝太阳&#xff0c;就只能看到自己的影子。 --纪伯伦语录 文章目录 简介准备常用命令查看代码&#xff08;list&#xff09;运行&#xff08;run&#xff09;打断点&#xff08;break&#xff09;逐语句&#xff08;step&#xff09;逐过程&#xff08;next&#xff09;完成…