Go 里的超时控制也很简单

26349904a145813cb0d8c06681d483a1.png

前言

日常开发中我们大概率会遇到超时控制的场景,比如一个批量耗时任务、网络请求等;一个良好的超时控制可以有效的避免一些问题(比如 goroutine 泄露、资源不释放等)。

Timer

在 go 中实现超时控制的方法非常简单,首先第一种方案是 Time.After(d Duration)

func main() {
 fmt.Println(time.Now())
 x := <-time.After(3 * time.Second)
 fmt.Println(x)
}

output:

2021-10-27 23:06:04.304596 +0800 CST m=+0.000085653
2021-10-27 23:06:07.306311 +0800 CST m=+3.001711390
ff1c51a9fa5afff7b278b6601581d5d6.png

time.After() 会返回一个 Channel,该 Channel 会在延时 d 段时间后写入数据。

有了这个特性就可以实现一些异步控制超时的场景:

func main() {
 ch := make(chan struct{}, 1)
 go func() {
  fmt.Println("do something...")
  time.Sleep(4*time.Second)
  ch<- struct{}{}
 }()
 
 select {
 case <-ch:
  fmt.Println("done")
 case <-time.After(3*time.Second):
  fmt.Println("timeout")
 }
}

这里假设有一个 goroutine 在跑一个耗时任务,利用 select 有一个 channel 获取到数据便退出的特性,当 goroutine 没有在有限时间内完成任务时,主 goroutine 便会退出,也就达到了超时的目的。

output:

do something...
timeout

timer.After 取消,同时 Channel 发出消息,也可以关闭通道等通知方式。

注意 Channel 最好是有大小,防止阻塞 goroutine ,导致泄露。

Context

第二种方案是利用 context,go 的 context 功能强大;194369ee6aaa2197098babf8fd12bf65.png利用 context.WithTimeout() 方法会返回一个具有超时功能的上下文。

ch := make(chan string)
 timeout, cancel := context.WithTimeout(context.Background(), 3*time.Second)
 defer cancel()
 go func() {
  time.Sleep(time.Second * 4)

  ch <- "done"
 }()

 select {
 case res := <-ch:
  fmt.Println(res)
 case <-timeout.Done():
  fmt.Println("timout", timeout.Err())
 }

同样的用法,contextDone() 函数会返回一个 channel,该 channel 会在当前工作完成或者是上下文取消生效。

timout context deadline exceeded

通过 timeout.Err() 也能知道当前 context 关闭的原因。

goroutine 传递 context

使用 context 还有一个好处是,可以利用其天然在多个 goroutine 中传递的特性,让所有传递了该 context 的 goroutine 同时接收到取消通知,这点在多 go 中应用非常广泛。

func main() {
 total := 12
 var num int32
 log.Println("begin")
 ctx, cancelFunc := context.WithTimeout(context.Background(), 3*time.Second)
 for i := 0; i < total; i++ {
  go func() {
   //time.Sleep(3 * time.Second)
   atomic.AddInt32(&num, 1)
   if atomic.LoadInt32(&num) == 10 {
    cancelFunc()
   }
  }()
 }
 for i := 0; i < 5; i++ {
  go func() {

   select {
   case <-ctx.Done():
    log.Println("ctx1 done", ctx.Err())
   }

   for i := 0; i < 2; i++ {
    go func() {
     select {
     case <-ctx.Done():
      log.Println("ctx2 done", ctx.Err())
     }
    }()
   }

  }()
 }

 time.Sleep(time.Second*5)
 log.Println("end", ctx.Err())
 fmt.Printf("执行完毕 %v", num)
}

在以上例子中,无论 goroutine 嵌套了多少层,都是可以在 context 取消时获得消息(当然前提是 context 得传递走)

某些特殊情况需要提前取消 context 时,也可以手动调用 cancelFunc() 函数。

Gin 中的案例

Gin 提供的 Shutdown(ctx) 函数也充分使用了 context

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 defer cancel()
 if err := srv.Shutdown(ctx); err != nil {
  log.Fatal("Server Shutdown:", err)
 }
 log.Println("Server exiting")
38e4fa3d9fd5d6abf3e5bf59e2a68e8f.png

比如以上代码便是超时等待 10s 进行 Gin 的资源释放,实现的原理也和上文的例子相同。

总结

因为写 go 的时间不长,所以自己写了一个练手的项目:一个接口压力测试工具。

e0c8284583165aac4544306aaa0cdb82.gif20e7d59ac74904fb62d7946cbb03e320.png

其中一个很常见的需求就是压测 N 秒后退出,这里正好就应用到了相关知识点,同样是初学 go 的小伙伴可以参考。

https://github.com/crossoverJie/ptg/blob/d0781fcb5551281cf6d90a86b70130149e1525a6/duration.go#L41

294812fd225e8422b9036cc2f804c6d4.png

观察者模式的实际应用


0fac950406405d33b5116b8a9e8dc729.png

[]*T *[]T *[]*T 傻傻分不清楚


ac21e7bbab62f3610c47a1b18683ed6b.png

Go 中的 channel 与 Java BlockingQueue 的本质区别


80f17d7dbe3ac654005b87ac1d0f4225.png

自带的 print 函数居然会报错?


d69115cbfd125a68dd698ad82caf938b.png

Pulsar:下一代消息引擎真的这么强吗?



a518383a5e8f3140b4d1ddeedc9ef4c7.png

写了一个 gorm 乐观锁插件


7d3039951881b622724aced48750d991.png

蜜月行之云南篇(上)


60665645a13959607b685040933ee8b9.png

一文搞懂参数传递原理


30971ece574a0bf6f20d98a24e0fa552.png

🤔一个 bug 引发的思考🤔


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书作者带你一步一步深入这些方法。你将理解 Go语言为何选定这些并发模型,这些模型又会带来什么问题,以及你如何组合利用这些模型中的原语去解决问题。学习那些让你在独立且自信的编写与实现任何规模并发系统时所需要用到的技巧和工具。 理解Go语言如何解决并发难以编写正确这一根本问题。 学习并发与并行的关键性区别。 深入到Go语言的内存同步原语。 利用这些模式中的原语编写可维护的并发代码。 将模式组合成为一系列的实践,使你能够编写大规模的分布式系统。 学习 goroutine 背后的复杂性,以及Go语言的运行时如何将所有东西连接在一起。 作者简介 · · · · · · Katherine Cox-Buday是一名计算机科学家,目前工作于 Simple online banking。她的业余爱好包括软件工程、创作、Go 语言(igo、baduk、weiquei) 以及音乐,这些都是她长期的追求,并且有着不同层面的贡献。 目录 · · · · · · 前言 1 第1章 并发概述 9 摩尔定律,Web Scale和我们所陷入的混乱 10 为什么并发很难? 12 竞争条件 13 原子性 15 内存访问同步 17 死锁、活锁和饥饿 20 确定并发安全 28 面对复杂性的简单性 31 第2章 对你的代码建模:通信顺序进程 33 并发与并行的区别 33 什么是CSP 37 如何帮助你 40 Go语言的并发哲学 43 第3章 Go语言并发组件 47 goroutine 47 sync包 58 WaitGroup 58 互斥锁和读写锁 60 cond 64 once 69 池 71 channel 76 select 语句 92 GOMAXPROCS控制 97 小结 98 第4章 Go语言的并发模式 99 约束 99 for-select循环103 防止goroutine泄漏 104 or-channel 109 错误处理112 pipeline 116 构建pipeline的最佳实践 120 一些便利的生成器 126 扇入,扇出 132 or-done-channel 137 tee-channel 139 桥接channel模式 140 队列排队143 context包 151 小结 168 第5章 大规模并发 169 异常传递169 超时和取消 178 心跳 184 复制请求197 速率限制199 治愈异常的goroutine 215 小结 222 第6章 goroutine和Go语言运行时 223 工作窃取223 窃取任务还是续体 231 向开发人员展示所有这些信息 240 尾声 240 附录A 241

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值