【Go】-Context库的使用

目录

为什么需要Context

基本示例

全局变量方式

通道方式

官方版的方案

Context初识

Context接口

Background()和TODO()

With系列函数

WithCancel

WithDeadline

WithTimeout

WithValue

使用Context的注意事项

客户端超时取消示例

server端

client端


为什么需要Context

        在 Go http包的Server中,每一个请求在都有一个对应的 goroutine 去处理。请求处理函数通常会启动额外的 goroutine 用来访问后端服务,比如数据库和RPC服务。

        用来处理一个请求的 goroutine 通常需要访问一些与请求特定的数据,比如终端用户的身份认证信息、验证相关的token、请求的截止时间。

        当一个请求被取消或超时时,所有用来处理该请求的 goroutine 都应该迅速退出,然后系统才能释放这些 goroutine 占用的资源。

基本示例

        在 Go 语言中,sync.WaitGroup 是一个用于控制多个协程(goroutine)的同步工具,它允许主协程等待多个并发协程完成工作。WaitGroup 通过计数器来跟踪多个协程的完成状态,每个协程在开始执行前调用 WaitGroupAdd 方法增加计数,执行完成后调用 Done 方法减少计数。当计数器归零时,表示所有协程都已完成,主协程可以继续执行。

主要方法

  • Add(delta int):增加 WaitGroup 的计数器,通常在启动协程之前调用。delta 可以是任何整数,通常用于跟踪需要等待的协程数量。
  • Done():减少 WaitGroup 的计数器,通常在协程完成工作后调用。
  • Wait():阻塞调用 Wait 的协程,直到 WaitGroup 的计数器归零。
package mainimport ("fmt""sync""time"
)var wg sync.WaitGroup
// 初始的例子
func worker() {for {fmt.Println("worker")time.Sleep(time.Second)}// 如何接收外部命令实现退出wg.Done()
}func main() {wg.Add(1)go worker()// 如何优雅的实现结束子goroutinewg.Wait()fmt.Println("over")
}

全局变量方式

        全局变量方式存在的问题:
                1. 使用全局变量在跨包调用时不容易统一
                2. 如果worker中再启动goroutine,就不太好控制了。

package mainimport ("fmt""sync""time"
)var wg sync.WaitGroup
var exit boolfunc worker() {for {fmt.Println("worker")time.Sleep(time.Second)if exit {break}}wg.Done()
}func main() {wg.Add(1)go worker()time.Sleep(time.Second * 3) // sleep3秒以免程序过快退出exit = true                 // 修改全局变量实现子goroutine的退出wg.Wait()fmt.Println("over")
}

通道方式

        管道方式存在的问题:
                1. 使用全局变量在跨包调用时不容易实现规范和统一,需要维护一个共用的channel

package mainimport ("fmt""sync""time"
)var wg sync.WaitGroupfunc worker(exitChan chan struct{}) {
LOOP:for {fmt.Println("worker")time.Sleep(time.Second)select {case <-exitChan: // 等待接收上级通知break LOOPdefault:}}wg.Done()
}func main() {var exitChan = make(chan struct{})wg.Add(1)go worker(exitChan)time.Sleep(time.Second * 3) // sleep3秒以免程序过快退出exitChan <- struct{}{}      // 给子goroutine发送退出信号close(exitChan)wg.Wait()fmt.Println("over")
}

官方版的方案

package mainimport ("context""fmt""sync""time"
)var wg sync.WaitGroupfunc worker(ctx context.Context) {
LOOP:for {fmt.Println("worker")time.Sleep(time.Second)select {case <-ctx.Done(): // 等待上级通知break LOOPdefault:}}wg.Done()
}func main() {ctx, cancel := context.WithCancel(context.Background())wg.Add(1)go worker(ctx)time.Sleep(time.Second * 3)cancel() // 通知子goroutine结束wg.Wait()fmt.Println("over")
}

        当子goroutine又开启另外一个goroutine时,只需要将ctx传入即可:

package mainimport ("context""fmt""sync""time"
)var wg sync.WaitGroupfunc worker(ctx context.Context) {go worker2(ctx)
LOOP:for {fmt.Println("worker")time.Sleep(time.Second)select {case <-ctx.Done(): // 等待上级通知break LOOPdefault:}}wg.Done()
}func worker2(ctx context.Context) {
LOOP:for {fmt.Println("worker2")time.Sleep(time.Second)select {case <-ctx.Done(): // 等待上级通知break LOOPdefault:}}
}
func main() {ctx, cancel := context.WithCancel(context.Background())wg.Add(1)go worker(ctx)time.Sleep(time.Second * 3)cancel() // 通知子goroutine结束wg.Wait()fmt.Println("over")
}

Context初识

        Go1.7加入了一个新的标准库context,它定义了Context类型,专门用来简化对于处理单个请求的多个 goroutine 之间与请求域的数据、取消信号、截止时间等相关操作,这些操作可能涉及多个 API 调用。

        对服务器传入的请求应该创建上下文,而对服务器的传出调用应该接受上下文。它们之间的函数调用链必须传递上下文,或者可以使用WithCancelWithDeadlineWithTimeoutWithValue创建的派生上下文。当一个上下文被取消时,它派生的所有上下文也被取消。


Context接口

    context.Context是一个接口,该接口定义了四个需要实现的方法。具体签名如下:

type Context interface {Deadline() (deadline time.Time, ok bool)Done() <-chan struct{}Err() errorValue(key interface{}) interface{}
}

其中:

  • Deadline方法需要返回当前Context被取消的时间,也就是完成工作的截止时间(deadline);
  • Done方法需要返回一个Channel,这个Channel会在当前工作完成或者上下文被取消之后关闭,多次调用Done方法会返回同一个Channel;
  • Err方法会返回当前Context结束的原因,它只会在Done返回的Channel被关闭时才会返回非空的值;
    • 如果当前Context被取消就会返回Canceled错误;
    • 如果当前Context超时就会返回DeadlineExceeded错误;
  • Value方法会从Context中返回键对应的值,对于同一个上下文来说,多次调用Value 并传入相同的Key会返回相同的结果,该方法仅用于传递跨API和进程间跟请求域的数据;

Background()和TODO()

        Go内置两个函数:Background()TODO(),这两个函数分别返回一个实现了Context接口的backgroundtodo。我们代码中最开始都是以这两个内置的上下文对象作为最顶层的partent context,衍生出更多的子上下文对象。

    Background()主要用于main函数、初始化以及测试代码中,作为Context这个树结构的最顶层的Context,也就是根Context。

    TODO(),它目前还不知道具体的使用场景,如果我们不知道该使用什么Context的时候,可以使用这个。

    backgroundtodo本质上都是emptyCtx结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值的Context。


With系列函数

        此外,context包中还定义了四个With系列函数。

WithCancel

    WithCancel的函数签名如下:

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

    WithCancel返回带有新Done通道的父节点的副本。当调用返回的cancel函数或当关闭父上下文的Done通道时,将关闭副本上下文的Done通道,无论先发生什么情况。

        取消此上下文将释放与其关联的资源,因此代码应该在此上下文中运行的操作完成后立即调用cancel。

func gen(ctx context.Context) <-chan int {dst := make(chan int)n := 1go func() {for {select {case <-ctx.Done():return // return结束该goroutine,防止泄露case dst <- n:n++}}}()return dst
}func main() {ctx, cancel := context.WithCancel(context.Background())defer cancel() // 当我们取完需要的整数后调用cancelfor n := range gen(ctx) {fmt.Println(n)if n == 5 {break}}
}

        上面的示例代码中,gen函数在单独的goroutine中生成整数并将它们发送到返回的通道。 gen的调用者在使用生成的整数之后需要取消上下文,以免gen启动的内部goroutine发生泄漏。

WithDeadline

    WithDeadline的函数签名如下:

func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)

        返回父上下文的副本,并将deadline调整为不迟于d。如果父上下文的deadline已经早于d,则WithDeadline(parent, d)在语义上等同于父上下文。当截止日过期时,当调用返回的cancel函数时,或者当父上下文的Done通道关闭时,返回上下文的Done通道将被关闭,以最先发生的情况为准。

        取消此上下文将释放与其关联的资源,因此代码应该在此上下文中运行的操作完成后立即调用cancel。

func main() {d := time.Now().Add(50 * time.Millisecond)ctx, cancel := context.WithDeadline(context.Background(), d)// 尽管ctx会过期,但在任何情况下调用它的cancel函数都是很好的实践。// 如果不这样做,可能会使上下文及其父类存活的时间超过必要的时间。defer cancel()select {case <-time.After(1 * time.Second):fmt.Println("overslept")case <-ctx.Done():fmt.Println(ctx.Err())}
}

        上面的代码中,定义了一个50毫秒之后过期的deadline,然后我们调用context.WithDeadline(context.Background(), d)得到一个上下文(ctx)和一个取消函数(cancel),然后使用一个select让主程序陷入等待:等待1秒后打印overslept退出或者等待ctx过期后退出。

        在上面的示例代码中,因为ctx 50毫秒后就会过期,所以ctx.Done()会先接收到context到期通知,并且会打印ctx.Err()的内容。

WithTimeout

    WithTimeout的函数签名如下:

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

    WithTimeout返回WithDeadline(parent, time.Now().Add(timeout))

        取消此上下文将释放与其相关的资源,因此代码应该在此上下文中运行的操作完成后立即调用cancel,通常用于数据库或者网络连接的超时控制。具体示例如下:

package mainimport ("context""fmt""sync""time"
)// context.WithTimeoutvar wg sync.WaitGroupfunc worker(ctx context.Context) {
LOOP:for {fmt.Println("db connecting ...")time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒select {case <-ctx.Done(): // 50毫秒后自动调用break LOOPdefault:}}fmt.Println("worker done!")wg.Done()
}func main() {// 设置一个50毫秒的超时ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)wg.Add(1)go worker(ctx)time.Sleep(time.Second * 5)cancel() // 通知子goroutine结束wg.Wait()fmt.Println("over")
}

WithValue

    WithValue函数能够将请求作用域的数据与 Context 对象建立关系。声明如下:

func WithValue(parent Context, key, val interface{}) Context

    WithValue返回父节点的副本,其中与key关联的值为val。

        仅对API和进程间传递请求域的数据使用上下文值,而不是使用它来传递可选参数给函数。

        所提供的键必须是可比较的,并且不应该是string类型或任何其他内置类型,以避免使用上下文在包之间发生冲突。WithValue的用户应该为键定义自己的类型。为了避免在分配给interface{}时进行分配,上下文键通常具有具体类型struct{}。或者,导出的上下文关键变量的静态类型应该是指针或接口。

package mainimport ("context""fmt""sync""time"
)// context.WithValuetype TraceCode stringvar wg sync.WaitGroupfunc worker(ctx context.Context) {key := TraceCode("TRACE_CODE")traceCode, ok := ctx.Value(key).(string) // 在子goroutine中获取trace codeif !ok {fmt.Println("invalid trace code")}
LOOP:for {fmt.Printf("worker, trace code:%s\n", traceCode)time.Sleep(time.Millisecond * 10) // 假设正常连接数据库耗时10毫秒select {case <-ctx.Done(): // 50毫秒后自动调用break LOOPdefault:}}fmt.Println("worker done!")wg.Done()
}func main() {// 设置一个50毫秒的超时ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*50)// 在系统的入口中设置trace code传递给后续启动的goroutine实现日志数据聚合ctx = context.WithValue(ctx, TraceCode("TRACE_CODE"), "12512312234")wg.Add(1)go worker(ctx)time.Sleep(time.Second * 5)cancel() // 通知子goroutine结束wg.Wait()fmt.Println("over")
}

使用Context的注意事项

  • 推荐以参数的方式显示传递Context
  • 以Context作为参数的函数方法,应该把Context作为第一个参数。
  • 给一个函数方法传递Context的时候,不要传递nil,如果不知道传递什么,就使用context.TODO()
  • Context的Value相关方法应该传递请求域的必要数据,不应该用于传递可选参数
  • Context是线程安全的,可以放心的在多个goroutine中传递

客户端超时取消示例

        调用服务端API时如何在客户端实现超时控制?

server端

// context_timeout/server/main.go
package mainimport ("fmt""math/rand""net/http""time"
)// server端,随机出现慢响应
func indexHandler(w http.ResponseWriter, r *http.Request) {number := rand.Intn(2)if number == 0 {time.Sleep(time.Second * 10) // 耗时10秒的慢响应fmt.Fprintf(w, "slow response")return}fmt.Fprint(w, "quick response")
}func main() {http.HandleFunc("/", indexHandler)err := http.ListenAndServe(":8000", nil)if err != nil {panic(err)}
}

client端

// context_timeout/client/main.go
package mainimport ("context""fmt""io/ioutil""net/http""sync""time"
)// 客户端
type respData struct {resp *http.Responseerr  error
}func doCall(ctx context.Context) {transport := http.Transport{// 请求频繁可定义全局的client对象并启用长链接// 请求不频繁使用短链接DisableKeepAlives: true, 	}client := http.Client{Transport: &transport,}respChan := make(chan *respData, 1)req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)if err != nil {fmt.Printf("new requestg failed, err:%v\n", err)return}req = req.WithContext(ctx) // 使用带超时的ctx创建一个新的client requestvar wg sync.WaitGroupwg.Add(1)defer wg.Wait()go func() {resp, err := client.Do(req)fmt.Printf("client.do resp:%v, err:%v\n", resp, err)rd := &respData{resp: resp,err:  err,}respChan <- rdwg.Done()}()select {case <-ctx.Done()://transport.CancelRequest(req)fmt.Println("call api timeout")case result := <-respChan:fmt.Println("call server api success")if result.err != nil {fmt.Printf("call server api failed, err:%v\n", result.err)return}defer result.resp.Body.Close()data, _ := ioutil.ReadAll(result.resp.Body)fmt.Printf("resp:%v\n", string(data))}
}func main() {// 定义一个100毫秒的超时ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)defer cancel() // 调用cancel释放子goroutine资源doCall(ctx)
}

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

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

相关文章

【GBase 8c V5_3.0.0 分布式数据库常用维护命令】

一、查看数据库状态/检查&#xff08;gbase用户&#xff09; 1.gha_ctl monitor 使用gha_ctl monitor查看节点运行情况(跟dcs的地址和端口) gha_ctl monitor -c gbase -l http://172.20.10.8:2379 -Hall |coordinator | datanode | gtm | server|dcs:必选字段。指定查看哪类集…

程序员转行方向推荐

程序员转行方向推荐是一个涉及个人兴趣、技能匹配及市场需求等多方面因素的复杂话题。以下是一些详细的转行方向推荐&#xff0c;旨在帮助程序员在职业生涯中做出更加明智的选择。CSDN大礼包&#xff1a;《2024年最新全套学习资料包》免费分享 技术管理岗位 推荐理由&#xf…

崩坏星穹铁道PC端2.5版本剧情、奖励攻略 用GameViewer远程帮手机减负 随时畅玩星铁PC端

《崩坏&#xff1a;星穹铁道》2.5版本「碧羽飞黄射天狼」在9月10开启&#xff01;上半卡池有五星角色飞霄、知更鸟、卡芙卡、黑天鹅四位角色&#xff0c;还有2.5版本的新剧情&#xff0c;这一次崩铁上线送10连和 1000星琼等其他材料。由于游戏包体过大&#xff0c;不少玩家都选…

光伏开发:工商业光伏的流程管理全面解析

一、项目准备阶段 1、资源寻觅与沟通 首要任务是寻找适合的工商业屋顶或空地资源&#xff0c;并与业主初步交流&#xff0c;了解其意向、屋顶条件及用电情况。这一阶段的关键在于建立信任关系&#xff0c;为后续工作奠定基础。 2、资料收集与核查 全面收集业主资料&#xff…

2.ChatGPT的发展历程:从GPT-1到GPT-4(2/10)

引言 在人工智能领域&#xff0c;自然语言处理&#xff08;NLP&#xff09;是连接人类与机器的重要桥梁。随着技术的不断进步&#xff0c;我们见证了从简单的文本分析到复杂的语言理解的转变。ChatGPT&#xff0c;作为自然语言处理领域的一个里程碑&#xff0c;其发展历程不仅…

2_foc闭环调试_ADC电流采样与滤波及pid数据结构

1、ADC电流采样 上次添加了编码器获取电角度的程序&#xff0c;将之前开环控制的角度进行了替换&#xff0c;这次再将电流采样添加进来&#xff0c;之后就可以利用这样一个有反馈的系统进行电流环PI控制器参数调试。 之前写过ADC&#xff0b;DMA电流采样的stm32库函数程序&…

PPT中的图形与图片:插入、调整与格式设置技术详解

目录 引言 一、图形与图片的插入 1. 插入图形 2. 插入图片 二、图形与图片的调整 1. 调整大小与位置 2. 裁剪与旋转 3. 图形与图片的合并与组合 三、图片格式与布局设置 1. 图片格式设置 2. 图片布局设置 示例案例&#xff1a;制作产品展示PPT 四、结论 引言 在现…

浏览器查消息

window.addEventListener(message,function(event){console.log(Received message,event.data)}); 并把弹窗口对准要接收消息的ifrme 发消息的窗口

20240914 每日AI必读资讯

刚刚&#xff0c;OpenAI震撼发布o1大模型&#xff01;强化学习突破LLM推理极限 - OpenAI o1模型需要简单、直接的提示&#xff0c;而非复杂的指导。 - 避免使用思路链提示&#xff0c;因为o1模型已经具备内部推理能力。 - 使用分隔符来明确模型解析的部分&#xff0c;并限制…

网络编程Udp协议

文章目录 UDP协议1、什么是UDP协议&#xff1f;一、定义与基本概念二、主要特点三、报文格式四、应用场景五、总结 2、如何使用Java中的UDP套接字&#xff1f;一、UDP常用APIDatagramSocketDatagramPacket 二、UDP协议下的客户端-服务器服务器客户端 UDP协议 UDP协议&#xff…

微软发布Win11 24H2 九月累计更新补丁KB5043080!

系统之家于9月13日发出最新报道&#xff0c;微软面向Win11 24H2用户推送了九月最新更新补丁KB5043080&#xff0c;系统更新后&#xff0c;版本号将升至26100.1742。本次更新解决了任务管理器不正确显示的情况&#xff0c;还进行了多项改进。接下来&#xff0c;跟随小编一起深入…

小程序的右侧抽屉开关动画手写效果

<template><view><button click"openDrawer">打开抽屉</button><view v-if"showDrawer" class"drawer" :style"{ backgroundColor: bgColor }" click"closeDrawer"><view class"draw…

【系统架构设计师-2014年真题】案例分析-答案及详解

更多内容请见: 备考系统架构设计师-核心总结索引 文章目录 【材料1】问题1问题2【材料2】问题1问题2问题3【材料3】问题1问题2问题3【材料4】问题1问题2【材料5】问题1问题2问题3【材料1】 请详细阅读以下关于网络设备管理系统架构设计的说明,在答题纸上回答问题1和问题2。 …

【开发环境搭建】Macbook M1搭建Java开发环境

JDK 安装与配置 下载并安装 JDK&#xff1a; ARM64 DMG 安装包下载链接&#xff1a;JDK21 for Mac (ARM64)。双击下载的 DMG 文件&#xff0c;按照提示安装 JDK。 配置环境变量&#xff1a; 打开终端&#xff0c;使用 vim 编辑 .bash_profile 文件&#xff1a; vim ~/.bash_pr…

微信小程序登录与获取手机号 (Python)

文章目录 相关术语登录逻辑登录设计登录代码 相关术语 调用接口[wx.login()]获取登录凭证&#xff08;code&#xff09;。通过凭证进而换取用户登录态信息&#xff0c;包括用户在当前小程序的唯一标识&#xff08;openid&#xff09;、微信开放平台账号下的唯一标识&#xff0…

Unity 之 【Android Unity FBO渲染】之 [Unity 渲染 Android 端播放的视频] 的一种方法简单整理

Unity 之 【Android Unity FBO渲染】之 [Unity 渲染 Android 端播放的视频] 的一种方法简单整理 目录 Unity 之 【Android Unity FBO渲染】之 [Unity 渲染 Android 端播放的视频] 的一种方法简单整理 一、简单介绍 二、FBO 简单介绍 三、案例实现原理 四、注意事项 五、简…

利用熵权法进行数值评分计算——算法过程

1、概述 在软件系统中&#xff0c;研发人员常常遇上需要对系统内的某种行为/模型进行评分的情况。例如根据系统的各种漏洞情况对系统安全性进行评分、根据业务员最近操作系统的情况对业务员工作状态进行打分等等。显然研发人员了解一种或者几种标准评分算法是非常有利于开展研…

word文档无损原样转pdf在windows平台使用python调用win32com使用pip安装pywin32

前提&#xff1a; windows环境下&#xff0c;并且安装了office套装&#xff0c;比如word,如果需要调用excel.也需要安装。在另外的文章会介绍。这种是直接调用word的。所以还原度会比较高。 需求&#xff1a; word文档转pdf,要求使用命令行形式&#xff0c;最终发布为api接口…

数据库基础知识---------------------------(1)

数据库分类 关系型数据库 以表格方式存储数据 例子&#xff1a; MySQL、Oracle、DB2、SQLserver等 特点&#xff1a; SQL结构程度较高、安全性高、查询效率较低 非关系型数据库 以键值方式存储数据 例子&#xff1a; Redis、Hbase、MongoDB等 特点&#xff1a; 查询效率…

(不用互三)AI绘画工具大比拼:Midjourney VS Stable Diffusion该如何选择?

文章目录 &#x1f4af;如何选择合适的AI绘画工具根据个人需求选择1. 您喜欢什么风格的绘画&#xff1f;2. 您想要创作什么主题的内容&#xff1f;3. 您对绘画工具的使用经验如何&#xff1f; 比较工具特点1. 工具的易用性和功能性如何&#xff1f;易用性&#xff1a;功能性&am…