【golang】Context超时控制与原理

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

Context

在Go语言圈子中流行着一句话:

Never start a goroutine without knowing how it will stop。

翻译:如果你不知道协程如何退出,就不要使用它。

在创建协程时,我们可能还会再创建一些别的子协程,那么这些协程的退出就成了问题。在Go1.7之后,Go官方引入了Context来实现协程的退出。不仅如此,Context还提供了跨协程、甚至是跨服务的退出管理。

Context本身的含义是上下文,我们可以理解为它内部携带了超时信息、退出信号,以及其他一些上下文相关的值(例如携带本次请求中上下游的唯一标识trace_id)。由于Context携带了上下文信息,父子协程之间就可以”联动“ 了。

Context标准库

在Context标准库中重要的结构 context.Context其实是一个接口,它提供了Deadline、Done、Err、Value这4种方法:

type Context interface {
   Deadline() (deadline time.Time, ok bool)
   Done() <-chan struct{}
   Err() error
   Value(key interface{}) interface{}
 }
  • Deadline方法用于返回Context的过期时间。Deadline第一个返回值表示Context的过期时间,第二个返回值表示是否设置了过期时间,如果多次调用Deadline方法会返回相同的值。

  • Done是使用最频繁的方法,它会返回一个通道。一般的做法是调用者在select中监听该通道的信号,如果该通道关闭则表示服务超时或异常,需要执行后续退出逻辑。多次调用Done方法会返回相同的通道。

  • 通道关闭后,Err方法回返回退出的原因。

  • Value方法返回指定Key对应的value,这是Context携带的值。Key必须是可比较的,一般用法Key是一个全局变量,通过context.WithValue将key存储到Context中,并通过Context.Value方法退出。

Context是一个接口,这意味着需要有对应的具体实现。用户可以自己实现Context接口,并严格遵守Context接口。

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 interface{}) interface{} {
    return nil
}

因此,要具体使用Context,需要派生出新的Context。我们使用的最多的还是Go标准库中的实现。
前三个函数都用于派生出有退出功能的Context。

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
  • WithCancel函数回返回一个子Context和cancel方法。子Context会在两种情况下触发退出:一种情况是调用者主动调用了返回的cancel方法;另一种情况是当参数中的父Context退出时,子Context将级联退出。
  • WithTimeout函数指定超时时间。当超时发生后,子Context将退出。因此,子Context的退出有三种时机,一种是父Context退出;一种是超时退出;最后一种是主动调用cancel函数退出。
  • WithDeadline和WithTimeout函数的处理方法相似,不过它们的参数指定的是最后到期的时间。
  • WithValue函数会返回带key-value的子Context。

Context实践

eg:

下面的代码中childCtx是preCtx的子Context,其设置的超时时间为300ms。但是preCtx的超时时间为100ms,因此父Context退出后,子Context会立即退出,实际的等待时间只有100ms。

func main() {
   ctx := context.Background()
   before := time.Now()
   preCtx, _ := context.WithTimeout(ctx, 100*time.Millisecond)
   
   go func() {
   childCtx, _ := context.WithTimeout(preCtx, 300*time.Millisecond)
   select {
    case <-childCtx.Done():
   after := time.Now()
   fmt.Println("child during:", after.Sub(before).Milliseconds())
   }
 }()
 
 select {
    case <-preCtx.Done():
    after := time.Now()
    fmt.Println("pre during:", after.Sub(before).Milliseconds())
 }
 }

这是输出如下,父Context与子Context退出的时间差接近100ms:

pre during: 104
child during: 104

当我们把preCtx的超时时间修改为500ms时:

preCtx ,_:= context.WithTimeout(ctx,500*time.Millisecond)

从新的输出中可以看出,子协程的退出不会影响父协程的退出。

child during: 304
pre during: 500

Context底层原理

Context在很大程度上利用了通道的一个特性:通道在close时,会通知所有监听它的协程。

每个派生出的子Context都会创建一个新的退出通道,这样,只要组织好Context之间的关系,就可以实现继承链上退出信号的传递。如图所示的三个协程中,关闭通道A会连带关闭调用链上的通道B,通道B会关闭通道C。
【golang】Context超时控制与原理,golang,golang
要使用context的退出功能,需要调用WithCancel或WithTimeout,派生出一个新的结构Context。WithCancel底层对应的结构为cancelCtx,WithTimeout底层对应的结构为timerCtx,timerCtx包装了cancelCtx,并存储了超时时间。

type cancelCtx struct {
	Context

	mu       sync.Mutex            // protects following fields
	done     atomic.Value          // of chan struct{}, created lazily, closed by first cancel call
	children map[canceler]struct{} // set to nil by the first cancel call
	err      error                 // set to non-nil by the first cancel call
	cause    error                 // set to non-nil by the first cancel call
}

type timerCtx struct {
	cancelCtx
	timer *time.Timer // Under cancelCtx.mu.

	deadline time.Time
}

cancelCtx第一个字段保留了父Context的信息。children字段则保存了当前Context派生的子Context的信息,每个Context都会有一个单独的done通道。

而WithDeadline函数会先判断父Context设置的超时时间是否比当前Context的超时时间短,如果是,那么子协程会随着父Context的退出而退出,没有必要再设置定时器。

当我们使用了标准库中默认的Context实现时,propagateCancel函数将子Context加入父协程的children哈希表中,并开启一个定时器。当定时器到期时,会调用cancel方法关闭通道,级联关闭当前Context派生的子Context,并取消与父Context的绑定关系。这种特性就产生了调用链上连锁的退出反应。文章来源地址https://www.toymoban.com/news/detail-815050.html

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

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

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

相关文章

  • golang Context应用举例

      golang标准库里Context实际上是一个接口(即一种编程规范、 一种约定)。   通过查看源码里的注释,我们得到如下约定: Done()函数返回一个只读管道,且管道里不存放任何元素(struct{}),所以用这个管道就是为了实现阻塞 Deadline()用来记录到期时间,以及是否到期。 Err()用来

    2024年02月08日
    浏览(27)
  • golang中context详解

    编码中遇到上下文信息传递,并发信息取消等,记录下在go语言中context包的使用。 在Go语言中,context包提供了一种在程序中传递截止日期、取消信号、请求范围数据和其他元数据的方式。context包的核心类型是Context接口,它定义了在执行上下文中传递的方法。Context接口的主要

    2024年01月21日
    浏览(23)
  • Golang:浅析Context包

    在golang官方文档context package - context - Go Packages中是这样介绍context包的: 在context包中定义了context类型来在不同的Goroutine 之间传递上下文,携带截止时间、取消信号以及携带上下文的系统参数(k-v)的类型。对服务器的传入请求应该创建上下文,对服务器的传出调用应该接受上

    2024年02月06日
    浏览(30)
  • golang之context实用记录

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

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

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

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

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

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

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

    2024年02月09日
    浏览(46)
  • 已解决org.springframework.web.context.request.async.AsyncRequestTimeoutException异步请求超时异常的正确解决方法,亲测有效!!!

    已解决org.springframework.web.context.request.async.AsyncRequestTimeoutException异步请求超时异常的正确解决方法,亲测有效!!! 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 总结 在基于Spring Framework构建的现代Web应用程序中,异步处理是提高性能和用户体验的一种常用技

    2024年03月27日
    浏览(38)
  • 网路原理-传输层UDP,TCP/IP(确认应答,超时重传,连接管理,三次握手,四次挥手,状态转换,流量控制,滑动窗口,拥塞控制,延时应答,捎带应答,异常情况,面向字节流)-网络层(IP协议,地址管理)

    本节重点 • 理解传输层的作⽤,深⼊理解TCP的各项特性和机制 • 对整个TCP/IP协议有系统的理解 • 对TCP/IP协议体系下的其他重要协议和技术有⼀定的了解 我们之前编写完了基本的 java socket ,要知道,我们之前所写的所有代码都在应⽤层,都是为了 完成某项业务,如翻译等。

    2024年04月15日
    浏览(42)
  • golang: 模仿 VictoriaMetrics 中的做法,通过把局部变量放在自定义 Context 对象中来做到hot path 的 0 alloc

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 使用 benchmark 压测过程中通常会出现这样的信息: 可以看见 f1 在每次运行都产生了 28 次内存分配。 gc 通常是 golang 最大的性能杀手,减少内存分配对性能提升非常明显

    2024年02月17日
    浏览(50)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包