首页 > 其他分享 >Golang 中的 Context 包

Golang 中的 Context 包

时间:2023-11-13 13:34:44浏览次数:40  
标签:Context 示例 ctx Golang context time cancel 上下文

Golang 中的 Context 包

原创 Slagga 技术的游戏 2023-11-12 12:28 发表于广东 收录于合集#Golang89个 图片

简介

今天,我们将讨论 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 seconds
    fmt.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 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 <-time.After(1 * time.Second):
            // The task is done, we're ready to exit
            fmt.Println("Task is done")
            return
        case <-ctx.Done():
            // 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
    <-getShutdownSignal()

    // 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 在 <-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 operation
    select {
    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 seconds
    time.Sleep(2 * time.Second)
    cancel()

    // Give the tasks some time to finish up
    time.Sleep(1 * time.Second)
}

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

仍然在尝试理解吗?

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

package main

import (
 "context"
 "fmt"
 "math/rand"
 "time"
)

func expensiveCalculation(ctx context.Context, resultChan chan<- int) {
 // 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 <-ctx.Done():
  // Context was cancelled, don't write to the channel
  return
 default:
  // Write the result to the channel
  resultChan <- 42 // replace with your actual calculation result
 }
}

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 := <-resultChan:
  // Got the result
  fmt.Printf("Calculation completed with result: %d\n", res)
 case <-ctx.Done():
  // Context was cancelled
  fmt.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 中无法提前停止它。

 

Slagga

赞赏二维码喜欢作者

收录于合集 #Golang  89个 上一篇无缝集成GORM与Go Web框架 素材来源官方媒体/网络新闻 阅读 225 技术的游戏 ​ 喜欢此内容的人还喜欢   单体架构 vs 微服务架构的全面比较     技术的游戏 不看的原因   Go语言隐藏的接口陷阱:nil值判断的各种误区     Go先锋 不看的原因   在Golang中掌握并发和Goroutines     技术的游戏 不看的原因   写留言              

人划线

 

标签:Context,示例,ctx,Golang,context,time,cancel,上下文
From: https://www.cnblogs.com/cheyunhua/p/17828904.html

相关文章

  • golang http rpc
    server端:packagemainimport( "errors" "log" "net" "net/http" "net/rpc")typeArgsstruct{ A,Bint}typeQuotientstruct{ Quo,Remint}//定义typetypeArithintfunc(t*Arith)Multiply......
  • org.springframework.context.ApplicationContextException: Failed to start bean 'd
    这个原因是高版本SpringBoot整合swagger造成的我的项目是2.7.8swagger版本是3.0.0就会出现上面的报错解决方式:1.配置WebMvcConfigurer.javaimportorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.Res......
  • Golang布隆过滤器升级版
    作用:平常使用的布隆过滤器可以用来过滤Redis空数据,避免缓存穿透。升级点:将原本的bool数组位更改为int数组,实现便于删除操作的场景。代码如下:packagemainimport( "fmt")//BloomFilter布隆过滤器typeBloomFilterstruct{ bitArray[]int//升级版结构哈希所落位置+......
  • 想入坑golang web,向大佬们请教些问题?
    当你准备入坑Go语言的Web开发时,以下是一些常见的问题,你可以向大佬们请教:如何设置和启动一个GoWeb服务器?Go语言有哪些常用的Web开发框架?它们之间有什么区别和优劣势?Go语言中的路由是如何实现的?如何处理不同的HTTP请求方法和URL参数?Go语言如何处理请求和响应,以及如何......
  • golang json 序列化、反序列化 字符串反序列化
    golangjson序列化、反序列化字符串反序列化在使用Golang进行开发时,经常会遇到需要将一段JSON字符串进行序列化和反序列化的情况。JSON是一种轻量级数据交换格式,常用于前后端数据传输、存储等场景。Golang提供了内置的encoding/json包来处理JSON的序列化和反序列化。JSON的序列化......
  • 基于Golang协程实现流量统计系统项目开发
    基于Golang协程实现流量统计系统项目开发上一节课我们已经架设好了一个网站。,但是因为我们的网站没有流量。也生成不了大量的日志,靠我们自己点击生成那点日志也不够测试的。所以这次我们就用GO语言批量生成我们想要的日志。好了。我们开始写代码我用的IDE工具是GOLAND,没有为......
  • Golang使用nats
    nats自行安装packagemainimport( "fmt" "github.com/nats-io/nats.go")////nats-server在管理subject的时候是通过’.’进行分割的,server底层是使用treemodule分层管理subject.此处有两个通配符*和>。////*可以匹配以.分割的一切。如:////nc.Subscribe("aa.*......
  • Golang struct 结构体注意事项和使用细节
    结构体所有字段在内存当中是连续的typePointstruct{ x,yint}typeRectstruct{ leftUp,rightDownPoint}funcmain(){ //r1会在内存当中有四个整数 r1:=Rect{ leftUp:Point{ x:1, y:2, }, rightDown:Point{ x:3, y:4, }, } //r1有......
  • Golang锁简单使用
    golang主要有两种锁:互斥锁和读写锁互斥锁Mutex用于提供一种加锁机制(LockingMechanism),保证同一时刻只有一个goroutine在临界区运行packagemainimport( "fmt" "sync" "time")funcmain(){ varmutexsync.Mutex x:=0 gofunc(){ mutex.Lock() x=x+1......
  • Golang服务端断线重连
    断线重连的逻辑很简单,就是把用户存到服务器内存中,当客户端再次登录的时候,判断内存中是否有用户的值,有的话替换packagemainimport( "fmt" "github.com/gorilla/websocket" "log" "net/http" "sync" "time")typeClientstruct{ conn*we......