您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > Go语言

Go语言Context应用全攻略:异步编程利器

时间:2023-11-06 14:40:37  来源:微信公众号  作者:Go先锋

概述

在 Go 语言中,Context(上下文)是一个非常重要的概念,特别是在处理请求时。

允许在请求的整个生命周期内传递数据、控制请求的取消、处理超时等。

本文将介绍 Go 语言中 Context 的使用,帮助更好地理解与处理请求的传递与控制。

主要内容包括

Context 基础

Context 创建与传递

Context 的超时与取消

Context 的链式操作

Context 在并发中的应用

Context 的应用场景

最佳实践与注意事项

1. Context 基础

在 Go 语言中,context.Context 接口定义了一个请求的上下文。

它包含了请求的截止时间、取消信号和请求的数据。

使用 Context 可以在请求之间有效地传递数据,同时也可以控制请求的生命周期。

type Context interface {


Deadline() (deadline time.Time, ok bool)


Done() <-chan struct{}


Err() error


Value(key interface{}) interface{}
}

Context 接口包含了四个方法:Deadline() 返回 Context 的截止时间

Done() 返回一个通道,它会在 Context 被取消或超时时关闭

Err() 返回 Context 的错误信息,Value(key) 返回 Context 中与 key 关联的值。

2. Context 创建与传递

2.1 创建和传递 Context

package mAIn

import (
  "context"
  "fmt"
  "time"
)


func main() {
  // 创建一个根Context
  rootContext := context.Background()


  // 创建一个带有超时时间的Context,这里设置超时时间为2秒
  ctx, cancel := context.WithTimeout(rootContext, 2*time.Second)
  defer cancel()


  // 在新的goroutine中执行任务
  go func(ctx context.Context) {
    select {
    case <-time.After(3 * time.Second):
      fmt.Println("任务完成")
    case <-ctx.Done():
      fmt.Println("任务取消或超时")
    }
  }(ctx)
  
  // 等待一段时间,模拟程序运行
  time.Sleep(5 * time.Second)
}

在这个例子中,创建了一个带有 2 秒超时时间的 Context,并在一个新的 goroutine 中执行一个任务。

在主 goroutine 中,等待了 5 秒,因此任务在超时之前完成,所以会输出"任务完成"。

2.2 使用 WithValue 传递数据

package main


import (
  "context"
  "fmt"
)


type key string


func main() {
  // 创建一个根Context
  rootContext := context.Background()


  // 使用WithValue传递数据
  ctx := context.WithValue(rootContext, key("userID"), 123)


  // 在子函数中获取传递的数据
  getUserID(ctx)
}


func getUserID(ctx context.Context) {
  // 从Context中获取数据
  if userID, ok := ctx.Value(key("userID")).(int); ok {
    fmt.Println("UserID:", userID)
  } else {
    fmt.Println("UserID不存在")
  }
}

在这个示例中,使用 WithValue 方法在 Context 中传递了一个 userID 的值,并在 getUserID 函数中成功获取并打印了这个值。

3. Context 的超时与取消

3.1 设置请求超时时间

package main


import (
  "context"
  "fmt"
  "time"
)


func main() {
  // 创建一个根Context
  rootContext := context.Background()


  // 创建一个超时时间为2秒的Context
  timeoutCtx, _ := context.WithTimeout(rootContext, 2*time.Second)


  // 创建一个手动取消的Context
  cancelCtx, cancel := context.WithCancel(rootContext)
  defer cancel()


  // 在新的goroutine中执行任务
  go func(ctx context.Context) {
    select {
    case <-time.After(3 * time.Second):
      fmt.Println("任务完成")
    case <-ctx.Done():
      fmt.Println("任务取消或超时")
    }
  }(timeoutCtx)


  // 在另一个goroutine中执行任务
  go func(ctx context.Context) {
    select {
    case <-time.After(1 * time.Second):
      fmt.Println("另一个任务完成")
    case <-ctx.Done():
      fmt.Println("另一个任务取消")
    }
  }(cancelCtx)


  // 等待一段时间,模拟程序运行
  time.Sleep(5 * time.Second)
}

在上面例子中,用 WithTimeout 方法创建了一个带有 2 秒超时时间的 Context。

在任务的 goroutine 中,用 select 语句监听了超时和 Context 的取消两个事件,以便及时响应。

3.2 处理请求取消

package main


import (
  "context"
  "fmt"
  "time"
)


func main() {
  // 创建一个根Context
  rootContext := context.Background()


  // 创建一个可以手动取消的Context
  ctx, cancel := context.WithCancel(rootContext)
  defer cancel()


  // 在新的goroutine中执行任务
  go func(ctx context.Context) {
    select {
    case <-time.After(3 * time.Second):
      fmt.Println("任务完成")
    case <-ctx.Done():
      fmt.Println("任务取消")
    }
  }(ctx)


  // 等待一段时间,手动取消任务
  time.Sleep(2 * time.Second)
  cancel()


  // 等待一段时间,模拟程序运行
  time.Sleep(1 * time.Second)
}

在上面例子中,使用 WithCancel 方法创建了一个可以手动取消的 Context。

在主函数中,等待了 2 秒后,手动调用 cancel 函数取消了任务。

这时,在任务的 goroutine 中,ctx.Done() 会接收到取消信号,从而退出任务。

4. Context 的链式操作

在实际应用中,可能需要将多个 Context 串联起来使用。

Go 语言的 Context 提供了 WithCancel、WithDeadline、WithTimeout 等方法。

可以用这些方法实现多个 Context 的协同工作。

package main


import (
  "context"
  "fmt"
  "time"
)


func main() {
  // 创建一个根Context
  rootContext := context.Background()


  // 创建一个超时时间为2秒的Context
  timeoutCtx, _ := context.WithTimeout(rootContext, 2*time.Second)


  // 创建一个手动取消的Context
  cancelCtx, cancel := context.WithCancel(rootContext)
  defer cancel()


  // 在新的goroutine中执行任务
  go func(ctx context.Context) {
    select {
    case <-time.After(3 * time.Second):
      fmt.Println("任务完成")
    case <-ctx.Done():
      fmt.Println("任务取消或超时")
    }
  }(timeoutCtx)


  // 在另一个goroutine中执行任务
  go func(ctx context.Context) {
    select {
    case <-time.After(1 * time.Second):
      fmt.Println("另一个任务完成")
    case <-ctx.Done():
      fmt.Println("另一个任务取消")
    }
  }(cancelCtx)


  // 等待一段时间,模拟程序运行
  time.Sleep(5 * time.Second)
}

在示例中,创建了一个带有 2 秒超时时间的 Context 和一个可以手动取消的 Context,然后分别传递给两个不同的任务。

在主函数中,等待了 5 秒,超时时间为 2 秒,因此第一个任务会因超时而取消,第二个任务则会在 1 秒后完成。

5. Context 在并发中的应用

5.1 使用 Context 控制多个协程

package main


import (
  "context"
  "fmt"
  "sync"
  "time"
)


func main() {
  // 创建一个根Context
  rootContext := context.Background()


  // 创建一个可以手动取消的Context
  ctx, cancel := context.WithCancel(rootContext)
  defer cancel()


  // 使用WaitGroup等待所有任务完成
  var wg sync.WaitGroup


  // 启动多个协程执行任务
  for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(id int) {
      defer wg.Done()
      select {
      case <-time.After(time.Duration(id) * time.Second):
        fmt.Println("任务", id, "完成")
      case <-ctx.Done():
        fmt.Println("任务", id, "取消")
      }
    }(i)
  }


  // 等待一段时间,然后手动取消任务
  time.Sleep(2 * time.Second)
  cancel()


  // 等待所有任务完成
  wg.Wait()
}

在上面例子中,创建了一个可以手动取消的 Context,并使用 sync.WaitGroup 等待所有任务完成。

在 for 循环中,启动了 5 个协程,每个协程会等待一段时间后输出任务完成信息。

在主函数中,程序等待了 2 秒后,手动调用 cancel 函数取消了任务,协程会接收到取消信号并退出。

5.2 避免 Context 滥用

在使用 Context 时,要避免将 Context 放在结构体中。

因为 Context 应该作为函数参数传递,而不应该被放在结构体中进行传递。

Context 应该限定在程序的最小作用域,不要传递到不需要它的函数中。

6. Context 的应用场景

6.1 HTTP 请求中的 Context 使用

package main


import (
  "fmt"
  ".NET/http"
  "time"
)


func handler(w http.ResponseWriter, r *http.Request) {
  ctx := r.Context()


  select {
  case <-time.After(2 * time.Second):
    fmt.Fprintln(w, "Hello, World!")
  case <-ctx.Done():
    err := ctx.Err()
    fmt.Println("Server:", err)
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
}


func main() {
  http.HandleFunc("/", handler)
  http.ListenAndServe(":8080", nil)
}

在上面示例中,创建了一个 HTTP 请求处理函数 handler。

在处理函数中,用 r.Context() 获取到请求的 Context,并在其中执行一个耗时的任务。

如果请求超时,ctx.Done() 会接收到取消信号,可以在其中处理请求超时的逻辑。

6.2 数据库操作中的 Context 使用

package main


import (
  "context"
  "database/sql"
  "fmt"
  "time"


  _ "Github.com/go-sql-driver/MySQL"
)


func main() {
  // 连接数据库
  db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/database")
  if err != nil {
    fmt.Println("数据库连接失败:", err)
    return
  }
  defer db.Close()


  // 创建一个Context,设置超时时间为5秒
  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  defer cancel()


  // 在Context的超时时间内执行数据库查询
  rows, err := db.QueryContext(ctx, "SELECT * FROM users")
  if err != nil {
    fmt.Println("数据库查询失败:", err)
    return
  }
  defer rows.Close()


  // 处理查询结果
  for rows.Next() {
    // 处理每一行数据
  }
}

在上面例子中,使用 database/sql 包进行数据库查询。创建了一个带有 5 秒超时时间的 Context,并在其中执行数据库查询。

如果查询时间超过 5 秒,Context 会接收到取消信号,可以在其中执行处理查询超时的逻辑。

6.3 其他业务场景中的 Context 使用

在其他业务场景中,可使用 Context 实现更多复杂的任务协同。

例如,使用 Context 在多个微服务之间进行数据传递和超时控制。

以下是一个示例,演示了如何在微服务架构中使用 Context 进行跨服务的数据传递

package main

import (
  "context"
  "fmt"
  "time"
)


type Request struct {
  ID int
}


type Response struct {
  Message string
}


func microservice(ctx context.Context, reqCh chan Request, resCh chan Response) {
  for {
    select {
    case <-ctx.Done():
      fmt.Println("Microservice shutting down...")
      return
    case req := <-reqCh:
      // 模拟处理请求的耗时操作
      time.Sleep(2 * time.Second)
      response := Response{Message: fmt.Sprintf("Processed request with ID %d", req.ID)}
      resCh <- response
    }
  }
}


func main() {
  // 创建根Context
  rootContext := context.Background()


  // 创建用于请求和响应的通道
  reqCh := make(chan Request)
  resCh := make(chan Response)


  // 启动微服务
  go microservice(rootContext, reqCh, resCh)


  // 创建带有5秒超时时间的Context
  ctx, cancel := context.WithTimeout(rootContext, 5*time.Second)
  defer cancel()


  // 发送请求到微服务
  for i := 1; i <= 3; i++ {
    req := Request{ID: i}
    reqCh <- req


    select {
    case <-ctx.Done():
      fmt.Println("Request timed out!")
      return
    case res := <-resCh:
      fmt.Println(res.Message)
    }
  }
}

在上面示例中,创建了一个简单的微服务模拟,它接收来自 reqCh 通道的请求,并将处理结果发送到 resCh 通道。

在主函数中,用带有 5 秒超时时间的 Context 来确保请求不会无限期等待,同时也能够处理超时的情况。

7. 最佳实践与注意事项

7.1 避免在函数库中使用 Context

通常情况下,应该在函数的参数列表中显式传递 Context,而不是将 Context 放在结构体中。

这样做可以使函数的行为更加明确,避免隐藏传递的 Context,提高代码的可读性和可维护性。

7.2 避免在结构体中嵌入 Context

尽管可以将 Context 作为结构体的成员嵌入,但这样的做法通常是不推荐的。

因为 Context 应该是在函数调用的时候传递,而不是嵌入在结构体中。

如果结构体的方法需要使用 Context,应该将 Context 作为参数传递给这些方法。

7.3 注意 Context 的传递路径

在实际应用中,要仔细考虑 Context 的传递路径。

若是在多个函数之间传递 Context,确保 Context 的传递路径清晰明了,避免出现歧义和混乱。

Context 的传递路径应该尽量短,不要跨越过多的函数调用。

总结

在 Go 语言中,Context 是一个强大的工具,用于处理请求的传递、控制和超时等。

通过合理地使用 Context,可以编写出更加稳定、高效的异步程序,提高系统的健壮性和可维护性。



Tags:Go语言   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
宝藏级Go语言开源项目——教你自己动手开发互联网搜索引擎
DIYSearchEngine 是一个能够高速采集海量互联网数据的开源搜索引擎,采用 Go 语言开发。Github 地址:https://github.com/johnlui/DIYSearchEngine运行方法首先,给自己准备一杯...【详细内容】
2024-03-12  Search: Go语言  点击:(18)  评论:(0)  加入收藏
你是否想知道如何应对高并发?Go语言为你提供了答案!
并发编程是当前软件领域中不可忽视的一个关键概念。随着CPU等硬件的不断发展,我们都渴望让我们的程序运行速度更快、更快。而Go语言在语言层面天生支持并发,充分利用现代CPU的...【详细内容】
2023-12-29  Search: Go语言  点击:(107)  评论:(0)  加入收藏
Go语言实现GoF设计模式:适配器模式
简介适配器模式(Adapter)是最常用的结构型模式之一,在现实生活中,适配器模式也是处处可见,比如电源插头转换器,它可以让英式的插头工作在中式的插座上。GoF 对它的定义如下:Convert...【详细内容】
2023-12-12  Search: Go语言  点击:(207)  评论:(0)  加入收藏
Go语言字符串拼接方式与性能比较,分析过没?
在Go语言中,字符串拼接性能是相当高效的,主要原因有两点:一是字符串在Go中是不可变的(immutable),二是Go语言提供了strings.Builder类型来高效处理字符串拼接。1. 字符串是不可变...【详细内容】
2023-12-11  Search: Go语言  点击:(229)  评论:(0)  加入收藏
一篇学会AI与Go语言无缝对接
在当今应用开发领域,类似OpenAI API等生成式AI技术的蓬勃发展正在彻底改变着应用开发的格局。Python和JavaScript等语言已经拥有丰富的资源来支持这些技术,其中LangChain就是...【详细内容】
2023-12-04  Search: Go语言  点击:(141)  评论:(0)  加入收藏
20小时快速入门Go语言
Go语言是由Google开发的一种高效、简洁和并发性强的编程语言,其设计目标是使得程序员能够更加容易地创建可靠、高效的软件。尽管Go语言的语法相对其他编程语言来说可能更加陌...【详细内容】
2023-12-03  Search: Go语言  点击:(154)  评论:(0)  加入收藏
十个令人惊叹的Go语言技巧,让你的代码更加优雅
在开发生产项目的过程中,我注意到经常会发现自己在重复编写代码,使用某些技巧时没有意识到,直到后来回顾工作时才意识到。为了解决这个问题,我开发了一种解决方案,对我来说非常有...【详细内容】
2023-11-20  Search: Go语言  点击:(172)  评论:(0)  加入收藏
Go语言Context应用全攻略:异步编程利器
概述在 Go 语言中,Context(上下文)是一个非常重要的概念,特别是在处理请求时。允许在请求的整个生命周期内传递数据、控制请求的取消、处理超时等。本文将介绍 Go 语言中 Contex...【详细内容】
2023-11-06  Search: Go语言  点击:(304)  评论:(0)  加入收藏
Go语言高级特性:Context深入解读
概述在 Go 语言中,context(上下文)是一个非常重要的概念。它主要用于在多个 goroutine 之间传递请求特定任务的截止日期、取消信号以及其他请求范围的值。3. Context 的取消与...【详细内容】
2023-11-01  Search: Go语言  点击:(232)  评论:(0)  加入收藏
Go语言中如何实现JWT
什么JWTJWT(JSON Web Token)是一种开放标准(RFC 7519),定义了一种在各方之间安全传输信息的简洁方式。这些信息可以被验证和信任,因为它们是数字签名的。JWT由三部分组成,用.分隔。...【详细内容】
2023-09-11  Search: Go语言  点击:(250)  评论:(0)  加入收藏
▌简易百科推荐
宝藏级Go语言开源项目——教你自己动手开发互联网搜索引擎
DIYSearchEngine 是一个能够高速采集海量互联网数据的开源搜索引擎,采用 Go 语言开发。Github 地址:https://github.com/johnlui/DIYSearchEngine运行方法首先,给自己准备一杯...【详细内容】
2024-03-12  OSC开源社区    Tags:Go语言   点击:(18)  评论:(0)  加入收藏
Go Gin框架实现优雅地重启和停止
在Web应用程序中,有时候我们需要重启或停止服务器,无论是因为更新代码还是进行例行维护。在这种情景下,我们需要保证应用程序的可用性和数据的一致性。这就需要优雅地关闭和重...【详细内容】
2024-01-30  源自开发者  微信公众号  Tags:Go   点击:(67)  评论:(0)  加入收藏
如何让Go程序以后台进程或daemon方式运行
本文探讨了如何通过Go代码实现在后台运行的程序。最近我用Go语言开发了一个WebSocket服务,我希望它能在后台运行,并在异常退出时自动重新启动。我的整体思路是将程序转为后台...【详细内容】
2024-01-26  Go语言圈  微信公众号  Tags:Go程序   点击:(60)  评论:(0)  加入收藏
深入Go底层原理,重写Redis中间件实战
Go语言以其简洁、高效和并发性能而闻名,深入了解其底层原理可以帮助我们更好地利用其优势。在本文中,我们将探讨如何深入Go底层原理,以及如何利用这些知识重新实现一个简单的Re...【详细内容】
2024-01-25  547蓝色星球    Tags:Go   点击:(66)  评论:(0)  加入收藏
Go 内存优化与垃圾收集
Go提供了自动化的内存管理机制,但在某些情况下需要更精细的微调从而避免发生OOM错误。本文将讨论Go的垃圾收集器、应用程序内存优化以及如何防止OOM(Out-Of-Memory)错误。Go...【详细内容】
2024-01-15  DeepNoMind  微信公众号  Tags:Go   点击:(61)  评论:(0)  加入收藏
Go函数指针是如何让你的程序变慢的?
导读Go 语言的常规优化手段无需赘述,相信大家也能找到大量的经典教程。但基于 Go 的函数值问题,业界还没有太多深度讨论的内容分享。本文作者根据自己对 Go 代码的使用与调优...【详细内容】
2024-01-15  腾讯云开发者  微信公众号  Tags:Go函数   点击:(86)  评论:(0)  加入收藏
Go编程中调用外部命令的几种场景
在很多场合, 使用Go语言需要调用外部命令来完成一些特定的任务, 例如: 使用Go语言调用Linux命令来获取执行的结果,又或者调用第三方程序执行来完成额外的任务。在go的标准库...【详细内容】
2024-01-09  suntiger    Tags:Go编程   点击:(101)  评论:(0)  加入收藏
Go 语言不支持并发读写 Map,为什么?
Go语言的map类型不支持并发读写的主要原因是并发读写会导致数据竞态(data race),这意味着多个 goroutine 可能同时访问并修改同一个 map,从而引发不确定的结果。在Go语言的设计...【详细内容】
2024-01-05  Go语言圈  微信公众号  Tags:Go 语言   点击:(77)  评论:(0)  加入收藏
Go微服务入门到容器化实践
Go微服务入门到容器化实践Go 是一门高效、现代化、快速增长的编程语言,非常适合构建 Web 应用程序。而 Docker 是一种轻量级的容器化技术,能够使得您的应用程序在任何地方运行...【详细内容】
2024-01-01  大雷家吃饭    Tags:Go微服务   点击:(62)  评论:(0)  加入收藏
你是否想知道如何应对高并发?Go语言为你提供了答案!
并发编程是当前软件领域中不可忽视的一个关键概念。随着CPU等硬件的不断发展,我们都渴望让我们的程序运行速度更快、更快。而Go语言在语言层面天生支持并发,充分利用现代CPU的...【详细内容】
2023-12-29  灵墨AI探索室  微信公众号  Tags:Go语言   点击:(107)  评论:(0)  加入收藏
站内最新
站内热门
站内头条