golang Context应用举例

这篇具有很好参考价值的文章主要介绍了golang Context应用举例。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 文章来源地址https://www.toymoban.com/news/detail-710267.html

Context本质

golang标准库里Context实际上是一个接口(即一种编程规范、 一种约定)。

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

 

通过查看源码里的注释,我们得到如下约定:

  1. Done()函数返回一个只读管道,且管道里不存放任何元素(struct{}),所以用这个管道就是为了实现阻塞
  2. Deadline()用来记录到期时间,以及是否到期。
  3. Err()用来记录Done()管道关闭的原因,比如可能是因为超时,也可能是因为被强行Cancel了。
  4. Value()用来返回key对应的value,你可以想像成Context内部维护了一个map。

Context实现

go源码里提供了Context接口的一个具体实现,遗憾的是它只是一个空的Context,什么也没做。

type emptyCtx int

func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
    return
}

func (*emptyCtx) Done() <-chan struct{} {
    return nil
}

func (*emptyCtx) Err() error {
    return nil
}

func (*emptyCtx) Value(key any) any {
    return nil
}

 

emptyCtx以小写开头,包外不可见,所以golang又提供了Background和TODO这2个函数让我们能获取到emptyCtx。

var (
        background = new(emptyCtx)
        todo       = new(emptyCtx)
)
func Background() Context {
        return background
}
func TODO() Context {
        return todo
}

 

backgroud和todo明明是一模一样的东西,就是emptyCtx,为什么要搞2个呢?真心求教,知道的同学请在评论区告诉我。

emptyCtx有什么用?创建Context时通常需要传递一个父Context,emptyCtx用来充当最初的那个Root Context。

With Value

当业务逻辑比较复杂,函数调用链很长时,参数传递会很复杂,如下图:

f1产生的参数b要传给f2,虽然f2并不需要参数b,但f3需要,所以b还是得往后传。

如果把每一步产生的新变量都放到Context这个大容器里,函数之间只传递Context,需要什么变量时直接从Context里取,如下图:

golang Context应用举例

 

f2能从context里取到a和b,f4能从context里取到a、b、c、d。

package main

import (
    "context"
    "fmt"
)

func step1(ctx context.Context) context.Context {
    //根据父context创建子context,创建context时允许设置一个<key,value>对,key和value可以是任意数据类型
    child := context.WithValue(ctx, "name", "大脸猫")
    return child
}

func step2(ctx context.Context) context.Context {
    fmt.Printf("name %s\n", ctx.Value("name"))
    //子context继承了父context里的所有key value
    child := context.WithValue(ctx, "age", 18)
    return child
}

func step3(ctx context.Context) {
    fmt.Printf("name %s\n", ctx.Value("name")) //取出key对应的value
    fmt.Printf("age %d\n", ctx.Value("age"))
}

func main1() {
    grandpa := context.Background() //空context
    father := step1(grandpa)        //father里有一对<key,value>
    grandson := step2(father)       //grandson里有两对<key,value>
    step3(grandson)
}

 

Timeout

在视频 https://www.bilibili.com/video/BV1C14y127sv/ 里介绍了超时实现的核心原理,视频中演示的done管道可以用Context的Done()来替代,Context的Done()管道什么时候会被关系呢?2种情况:

1. 通过context.WithCancel创建一个context,调用cancel()时会关闭context.Done()管道。

func f1() {
    ctx, cancel := context.WithCancel(context.Background())
    go func() {
        time.Sleep(100 * time.Millisecond)
        cancel() //调用cancel,触发Done
    }()
    select {
    case <-time.After(300 * time.Millisecond):
        fmt.Println("未超时")
    case <-ctx.Done(): //ctx.Done()是一个管道,调用了cancel()都会关闭这个管道,然后读操作就会立即返回
        err := ctx.Err()        //如果发生Done(管道被关闭),Err返回Done的原因,可能是被Cancel了,也可能是超时了
        fmt.Println("超时:", err) //context canceled
    }
}

 

2. 通过context.WithTimeout创建一个context,当超过指定的时间或者调用cancel()时会关闭context.Done()管道。

func f2() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) //超时后会自动调用context的Deadline,Deadline会,触发Done
    defer cancel()
    select {
    case <-time.After(300 * time.Millisecond):
        fmt.Println("未超时")
    case <-ctx.Done(): //ctx.Done()是一个管道,context超时或者调用了cancel()都会关闭这个管道,然后读操作就会立即返回
        err := ctx.Err()        //如果发生Done(管道被关闭),Err返回Done的原因,可能是被Cancel了,也可能是超时了
        fmt.Println("超时:", err) //context deadline exceeded
    }
}

 

Timeout的继承问题

通过context.WithTimeout创建的Context,其寿命不会超过父Context的寿命。比如:

  1. 父Context设置了10号到期,5号诞生了子Context,子Context设置了100天后到期,则实际上10号的时候子Context也会到期。
  2. 父Context设置了10号到期,5号诞生了子Context,子Context设置了1天后到期,则实际上6号的时候子Context就会到期。
func inherit_timeout() {
    parent, cancel1 := context.WithTimeout(context.Background(), time.Millisecond*1000) //parent设置100ms超时
    t0 := time.Now()
    defer cancel1()

    time.Sleep(500 * time.Millisecond) //消耗掉500ms

    // child, cancel2 := context.WithTimeout(parent, time.Millisecond*1000) //parent还剩500ms,child设置了1000ms之后到期,child.Done()管道的关闭时刻以较早的为准,即500ms后到期
    child, cancel2 := context.WithTimeout(parent, time.Millisecond*100) //parent还剩500ms,child设置了100ms之后到期,child.Done()管道的关闭时刻以较早的为准,即100ms后到期
    t1 := time.Now()
    defer cancel2()

    select {
    case <-child.Done():
        t2 := time.Now()
        fmt.Println(t2.Sub(t0).Milliseconds(), t2.Sub(t1).Milliseconds())
        fmt.Println(child.Err()) //context deadline exceeded
    }
}

 

context超时在http请求中的实际应用

定心丸来了,最后说一遍:”context在实践中真的很有用“

客户端发起http请求时设置了一个2秒的超时时间:

package main
import (
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)

func main() {
    client := http.Client{
        Timeout: 2 * time.Second, //小于10秒,导致请求超时,会触发Server端的http.Request.Context的Done
    }
    if resp, err := client.Get("http://127.0.0.1:5678/"); err == nil {
        defer resp.Body.Close()
        fmt.Println(resp.StatusCode)
        if bs, err := ioutil.ReadAll(resp.Body); err == nil {
            fmt.Println(string(bs))
        }
    } else {
        fmt.Println(err) //Get "http://127.0.0.1:5678/": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
    }
}

 

服务端从Request里取提context,故意休息10秒钟,同时监听context.Done()管道有没有关闭。由于Request的context是2秒超时,所以服务端还没休息够context.Done()管道就关闭了。

package main
import (
    "fmt"
    "net/http"
    "time"
)

func welcome(w http.ResponseWriter, req *http.Request) {
    ctx := req.Context() //取得request的context
    select {
    case <-time.After(10 * time.Second): //故意慢一点,10秒后才返回结果
        fmt.Fprintf(w, "welcome")
    case <-ctx.Done(): //超时后client会撤销请求,触发ctx.cancel(),从而关闭Done()管道
        err := ctx.Err()            //如果发生Done(管道被关闭),Err返回Done的原因,可能是被Cancel了,也可能是超时了
        fmt.Println("server:", err) //context canceled
    }
}

func main() {
    http.HandleFunc("/", welcome)
    http.ListenAndServe(":5678", nil)
}

 

 

到了这里,关于golang Context应用举例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • golang之context实用记录

    简言 WithCancel()函数接受一个 Context 并返回其子Context和取消函数cancel 新创建协程中传入子Context做参数,且需监控子Context的Done通道,若收到消息,则退出 需要新协程结束时,在外面调用 cancel 函数,即会往子Context的Done通道发送消息 注意:当 父Context的 Done() 关闭的时候,子

    2024年02月09日
    浏览(31)
  • 【golang】Context超时控制与原理

    在Go语言圈子中流行着一句话: Never start a goroutine without knowing how it will stop。 翻译:如果你不知道协程如何退出,就不要使用它。 在创建协程时,我们可能还会再创建一些别的子协程,那么这些协程的退出就成了问题。在Go1.7之后,Go官方引入了Context来实现协程的退出。不仅

    2024年01月22日
    浏览(36)
  • Go语言并发之context标准库

    Go中的 goroutine 之间没有父与子的关系,也就没有所谓子进程退出后的通知机制,多个 goroutine 都是平行地 被调度,多个 goroutine 如何协作工作涉及通信、同步、通知和退出四个方面。 通信:chan 通道当然是 goroutine 之间通信的基础,注意这里的通信主要是指程序的数据通道。

    2024年02月09日
    浏览(38)
  • Golang中context包基础知识详解

    目录 什么是context.Context? 如何构造context.Context对象? 衍生Context方法 使用context包需要注意的点 context.Context是Golang标准库提供的接口(context包对此接口有多种实现),该接口提供了四个抽象法: Deadline方法,返回context.Context被取消的时间点,也就是需要完成任务的截止时间

    2024年02月02日
    浏览(43)
  • golang 通过context设置接口请求超时时间

    下面是直接可应用的实例:

    2024年02月10日
    浏览(41)
  • 【Golang】golang中http请求的context传递到异步任务的坑

    在golang中,context.Context可以用来用来设置截止日期、同步信号,传递请求相关值的结构体。 与 goroutine 有比较密切的关系。 在web程序中,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他的 goroutine去访问后端资源,比如数据库、RPC服务等,它们需要访

    2024年02月08日
    浏览(36)
  • 本质安全设备标准(IEC60079-11)的理解(四)

    IEC60079-11使用了较长的篇幅来说明设计中需要考虑到的各种间距, 这也从一定程度上说明了间距比较重要,在设计中是需要认真考虑。 从直觉上来讲,间距越大,电路越安全,因为间距比较大,不容易产生电弧。同时间距比较大,也易于散热,从温度角度来说,设备也比较安

    2024年02月15日
    浏览(76)
  • Golang gin middleware的编写与使用 context.Next函数

    在web应用服务中,完整的一个业务处理在技术上包含 客户端操作、服务器端处理、返回处理结果给客户端三个步骤。 在实际的业务开发和处理中,会有更负责的业务和需求场景。一个完整的系统可能要包含鉴权认证、权限管理、安全检查、日志记录等多维度的系统支持。 鉴

    2024年02月09日
    浏览(52)
  • IRQ Handler 的使用——以USART串口接收中断分别在标准库与HAL库版本下的举例

     前言: 1.中断系统及EXTI外部中断知识点见我的博文: 9.中断系统、EXTI外部中断_eirq-CSDN博客 文章浏览阅读301次,点赞7次,收藏6次。EXTI(Extern Interrupt)外部中断EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决

    2024年02月01日
    浏览(34)
  • Golang标准库——io

    原文:Golang标准库——io io包提供了对I/O原语的基本接口。本包的基本任务是包装这些原语已有的实现(如os包里的原语),使之成为共享的公共接口,这些公共接口抽象出了泛用的函数并附加了一些相关的原语的操作。 因为这些接口和原语是对底层实现完全不同的低水平操

    2024年02月07日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包