首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

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?

fmt.Println("Finished?the?task")

case?

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?seconds

ctx,?cancel?:=?context.WithTimeout(ctx,?3*time.Second)

defer?cancel()

//?Assume?db.QueryRowContext?is?a?function?that?executes?a?SQL?query?and?returns?a?row

row?:=?db.QueryRowContext(ctx,?"SELECT?name?FROM?users?WHERE?id?=??",?id)

var?name?string

if?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?seconds

ctx,?cancel?:=?context.WithTimeout(ctx,?5*time.Second)

defer?cancel()

//?Create?a?request?with?the?context

req,?err?:=?http.NewRequestWithContext(ctx,?http.MethodGet,?url,?nil)

if?err?!=?nil?{

return?nil,?err

}

//?Execute?the?request

resp,?err?:=?http.DefaultClient.Do(req)

if?err?!=?nil?{

return?nil,?err

}

defer?resp.Body.Close()

//?Parse?the?response?body?as?HTML

return?html.Parse(resp.Body),?nill

}

在这个示例中,如果从网站获取数据超过5秒,上下文将被取消,http.DefaultClient.Do应该返回一个错误。

示例3:长时间运行的任务

假设你有一个执行长时间运行任务的程序,但你希望能够在程序接收到关闭信号时停止任务。这在一个 Web 服务器中可能会很有用,当关闭时必须停止提供请求并进行清理。

func?doTask(ctx?context.Context)?{

for?{

select?{

case?

//?The?task?is?done,?we're?ready?to?exit

fmt.Println("Task?is?done")

return

case?

//?The?context?was?cancelled?from?the?outside,?clean?up?and?exit

fmt.Println("Got?cancel?signal,?cleaning?up")

return

}

}

}

func?main()?{

//?Create?a?new?context

ctx,?cancel?:=?context.WithCancel(context.Background())

//?Start?the?task?in?a?goroutine

go?doTask(ctx)

//?Wait?for?a?shutdown?signal

//?Cancel?the?context,?which?will?stop?the?task

cancel()

//?Wait?for?a?bit?to?allow?the?task?to?clean?up

time.Sleep(1?*?time.Second)

}

在这个示例中,当程序接收到关闭信号时,它会取消上下文,这会导致doTask在上接收到信号。

示例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?operation

select?{

case?

w.Write([]byte("Operation?finished."))

case?

w.Write([]byte("Operation?timed?out."))

}

})

http.ListenAndServe(":8080",?nil)

在这个示例中,如果操作需要超过2秒的时间,上下文将被取消,并且服务器将响应“操作超时”。

示例5:同步多个 Goroutines

假设你正在编写一个程序,使用 Goroutines 并发执行多个任务。如果其中一个任务失败,你希望取消所有其他任务。

func?doTask(ctx?context.Context,?id?int)?{

select?{

case?

fmt.Printf("Task?%v?finished.\n",?id)

case?

fmt.Printf("Task?%v?cancelled.\n",?id)

}

}

func?main()?{

ctx,?cancel?:=?context.WithCancel(context.Background())

for?i?:=?1;?i?

go?doTask(ctx,?i)

}

//?Cancel?the?context?after?2?seconds

time.Sleep(2?*?time.Second)

cancel()

//?Give?the?tasks?some?time?to?finish?up

time.Sleep(1?*?time.Second)

}

在这个示例中,当上下文被取消时,仍在运行的任何任务都将收到,从而允许它们进行清理并退出。

仍然在尝试理解吗?

当我第一次接触上下文时,我感到非常困惑,我提出了一个问题,即如果select前面的命令花费太长时间,那么我们永远无法检测到取消,这是一个合理的问题。因此,我准备了另一个示例来详细解释这种情况。

package?main

import?(

"context"

"fmt"

"math/rand"

"time"

)

func?expensiveCalculation(ctx?context.Context,?resultChan?chan

//?Simulate?a?long-running?calculation

rand.Seed(time.Now().UnixNano())

sleepTime?:=?time.Duration(rand.Intn(20)+1)?*?time.Second

fmt.Printf("Calculation?will?take?%s?to?complete\n",?sleepTime)

time.Sleep(sleepTime)

select?{

case?

//?Context?was?cancelled,?don't?write?to?the?channel

return

default:

//?Write?the?result?to?the?channel

resultChan?

}

}

func?main()?{

//?Create?a?context?that?will?be?cancelled?after?10?seconds

ctx,?cancel?:=?context.WithTimeout(context.Background(),?10*time.Second)

defer?cancel()?//?The?cancel?should?be?deferred?so?resources?are?cleaned?up

resultChan?:=?make(chan?int)

//?Start?the?expensive?calculation?in?a?separate?goroutine

go?expensiveCalculation(ctx,?resultChan)

//?Wait?for?either?the?result?or?the?context?to?be?done

select?{

case?res?:=?

//?Got?the?result

fmt.Printf("Calculation?completed?with?result:?%d\n",?res)

case?

//?Context?was?cancelled

fmt.Println("Calculation?cancelled")

}

}

time.Sleep(sleepTime)命令是阻塞的,将暂停 goroutine 的执行,直到指定的持续时间已过。这意味着select语句不会被执行,直到休眠时间已经过去。

然而,上下文的取消与 goroutine 内的执行是独立的。如果上下文的截止时间被超过或其cancel()函数被调用,它的Done()通道将被关闭。

在主 goroutine 中,您有另一个select语句,它将立即检测上下文的Done()通道是否已关闭,并在不等待expensiveCalculationgoroutine 完成休眠的情况下打印 **"Calculation cancelled"**。

也就是说,expensiveCalculationgoroutine 将在休眠后继续执行,它将在尝试写入resultChan之前检查上下文是否已被取消。如果已被取消,它将立即返回。这是为了避免潜在的死锁,如果没有其他goroutine从resultChan读取。

如果需要昂贵的计算(在本例中由time.Sleep模拟)在取消时立即停止,您必须设计计算以周期性地检查上下文是否已取消。这通常在需要将计算分解为较小部分的情况下使用循环。如果计算不能分解,并需要一次运行完毕,那么很遗憾,在 Go 中无法提前停止它。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OtrxyyBTn8QZkmHIjEoaomPg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
http://www.vxiaotou.com