golang超时不控制,那你还配是gopher?

简介: golang超时不控制,那你还配是gopher?

一个人说起自己时,便会得意忘形。——梅里美《卡门》


1. 前言



为什么超时需要控制,这个大家知道吗?自己在脑瓜子里面回想下曾经写过的超时控制代码。那么今天就给大家带来一篇超时控制实战篇,助你在项目中得心应手,废话不多说,直接上干货。


2. 为什么超时需要控制



  • 请求时间过长,用户侧可能已经离开本页面了,服务端还在消耗资源处理,得到的结果没有意义。
  • 过长时间的服务端处理会占用过多资源,导致并发能力下降,甚至出现不可用事故。


golang中一般一个请求是由多个串行或并行的子任务来完成的,每个子任务可能是另外的内部请求,那么当这个请求超时的时候,我们就需要快速返回,释放占用的资源,比如goroutine,文件描述符等。


3. 平民版逻辑



func Do(job interface{}) error {
  //模拟超时任务或者请求
 time.Sleep(time.Minute)
 return nil
}
func requestDo(ctx context.Context, job interface{}) error {
  return Do(job)
}

这样的实现可能用户无法忍受,因为一旦调用Do发生超时,用户侧就会等待很长一段时间没有响应,进而用户带着抱怨离开了页面,真是难过啊。


4. 精英版逻辑



func requestDo(ctx context.Context, job interface{}) error {
 ctx, cancel := context.WithTimeout(ctx, time.Second*2)
 defer cancel()
 done := make(chan error)
 go func() {
    //在这里调用超时控制请求或者任务
  done <- Do(job)
 }()
 select {
  case err := <-done: //请求返回
   return err
  case <-ctx.Done(): //超时控制
   return ctx.Err()
 }
}

大家发现问题了吗,亲爱的伙伴们,带着你们的慧眼,识别代码的bug,来来来。


好,那我给大家说下哪里有问题:

如果超时被控制返回了 即命中case <-ctx.Done(),那么直接就结束当前requestDo协称,但是我们的Do协称还在继续执行呢,当它准备返回的时候发现done没有goroutine接受了,那么Do协称就会一直卡在那里,导致协称泄漏啊


5. 超人版逻辑



解决上面bug的方案之一就是给done初始化一个size大小的空间。

done := make(chan error, 1)


这样的话,Do协称超时返回了,自动把结果写入done中,然后Do协称会自动退出,这样协称就不会泄漏了。


那有人会问,写进去之后会不会有影响啊,毕竟done没有人去接受了,那么这里明确告诉大家没有影响的,chan毕竟是一个对象,golang的channel资源是可以自动GC掉的


6. 如果Do发生panic了呢



如果Do发生panic,此时就会发现panic是无法被捕获的,原因是因为在 requestDo内部起的goroutine里产生的panic其它goroutine无法捕获。

解决办法就是:requestDo里加上panicChan来处理,同样,需要 panicChan的buffer size为1,如下


panicChan := make(chan interface{}, 1)


详细请看:

func requestDo(ctx context.Context, job interface{}) error {
 ctx, cancel := context.WithTimeout(ctx, time.Second*2)
 defer cancel()
 done := make(chan error)
 panicChan := make(chan interface{}, 1)
 go func() {
  defer func() {
   if p := recover(); p != nil {
    panicChan <- p
   }
  }()
  done <- Do(job)
 }()
 select {
  case err := <-done:
   return err
  case p := <-panicChan: 
   panic(p) //这里也需要panic
  case <-ctx.Done():
   return ctx.Err()
 }
}

改完就可以在requestDo的调用方处理panic 了。


7. 小结



大家看到了吧,通过以上知识点的学习,我相信大家可以写出更加健全的超时控制逻辑了,那还在等什么,快快实践吧。


8. 关注公众号



 微信公众号:堆栈future

相关文章
|
5月前
|
数据管理 Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第25天】Go语言中的`context`包在并发、网络请求和长任务中至关重要,提供取消、截止时间和元数据管理。本文探讨`context`基础,如`Background()`、`TODO()`、`WithCancel()`、`WithDeadline()`和`WithTimeout()`。常见问题包括不当传递、过度使用`Background()`和`TODO()`以及忽略错误处理。通过取消和超时示例,强调正确传递上下文、处理取消错误和设置超时以提高应用健壮性和响应性。正确使用`context`是构建稳定高效Go应用的关键。
87 1
|
5月前
|
Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第23天】Go语言的`context`包提供`Context`接口用于处理任务取消、超时和截止日期。通过传递`Context`对象,开发者能轻松实现复杂控制流。本文解析`context`包特性,讨论常见问题和解决方案,并给出代码示例。关键点包括:1) 确保将`Context`传递给所有相关任务;2) 根据需求选择适当的`Context`创建函数;3) 定期检查`Done()`通道以响应取消请求。正确使用`context`包能提升Go程序的控制流管理效率。
61 1
|
Linux Go C++
golang select 机制和超时
golang select 机制和超时
108 0
|
1月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
87 4
Golang语言之管道channel快速入门篇
|
1月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
56 4
Golang语言文件操作快速入门篇
|
1月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
76 3
Golang语言之gRPC程序设计示例
|
1月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
69 4
|
1月前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
43 3
|
7天前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
21 0