前言

日常开发中我们大概率会遇到超时控制的场景,比如一个批量耗时任务、网络请求等;一个良好的超时控制可以有效的避免一些问题(比如 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

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 功能强大;



利用 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")

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

总结

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



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

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

Go 里的超时控制的更多相关文章

  1. .net Session 超时控制

    webconfig里明明设置了timeout是很大的数值了,可是session的有效性仍然无法维持一小时. 查了一下,主要是说 mode="InProc" 的话,session是放 ...

  2. ExtJS4.2.1与Spring MVC实现Session超时控制

    假设你的项目使用ExtJS作为表现层.你会发现,SESSION超时控制将是一个问题. 本文将就自己的经验.来解决这一问题.当然,解决这个问题并不是仅仅有一种方法,我仅仅是提出我的方法. 首先.做超时控 ...

  3. Go 采用 time.After 实现超时控制

    场景: 假设业务中需调用服务接口A,要求超时时间为5秒,那么如何优雅.简洁的实现呢? 我们可以采用select+time.After的方式,十分简单适用的实现. time.After()表示time. ...

  4. 一文搞懂如何实现 Go 超时控制

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

  5. 【小程序分享篇 一 】开发了个JAVA小程序, 用于清除内存卡或者U盘里的垃圾文件非常有用

    有一种场景, 手机内存卡空间被用光了,但又不知道哪个文件占用了太大,一个个文件夹去找又太麻烦,所以我开发了个小程序把手机所有文件(包括路径下所有层次子文件夹下的文件)进行一个排序,这样你就可以找出哪个 ...

  6. 在.NET Core 里使用 BouncyCastle 的DES加密算法

    .NET Core上面的DES等加密算法要等到1.2 才支持,我们可是急需这个算法的支持,文章<使用 JavaScriptService 在.NET Core 里实现DES加密算法>需要用 ...

  7. 使用 JavaScriptService 在.NET Core 里实现DES加密算法

    文章<ASP.NET Core love JavaScript>和<跨平台的 NodeJS 组件解决 .NetCore 不支持 System.Drawing图形功能的若干问题> ...

  8. .NET Core系列 : 2 、project.json 这葫芦里卖的什么药

    .NET Core系列 : 1..NET Core 环境搭建和命令行CLI入门 介绍了.NET Core环境,本文介绍.NET Core中最重要的一个配置文件project.json的相关内容.我们可 ...

  9. .NET里简易实现AOP

    .NET里简易实现AOP 前言 在MVC的过滤器章节中对于过滤器的使用就是AOP的一个实现了吧,时常在工作学习中遇到AOP对于它的运用可以说是很熟练了,就是没想过如果自己来实现的话是怎么实现的,性子比 ...

随机推荐

  1. openswan协商流程之(五):main_inR2_outI3()

    主模式第五包:main_inR2_outI3 文章目录 主模式第五包:main_inR2_outI3 1. 序言 2.函数调用关系 3. 第五个报文流程图 4. main_inR2_outI3()源码 ...

  2. 深入xLua实现原理之C#如何调用Lua

    本文主要是探讨xLua下C#调用Lua的实现原理,有关Lua如何调用C#的介绍可以查看深入xLua实现原理之Lua如何调用C# C#与Lua数据通信机制 无论是Lua调用C#,还是C#调用Lua,都需 ...

  3. Java面向对象系列(9)- 方法重写

    为什么需要重写? 父类的功能,子类不一定需要,或者不一定满足 场景一 重写都是方法的重写,和属性无关 父类的引用指向了子类 用B类新建了A类的对象,把A赋值给了B,这时候B是A,A又继承了B类,向上转 ...

  4. jmeter之聚合报告(Aggregate Report)

    jmeter最常用的listener--聚合报告Aggregate Report,每一个字段的具体含义是什么? Label:每个请求的名称.每个 JMeter 的 element(例如 HTTP Re ...

  5. Jenkins持续集成体系 | 最完整的介绍及资料

    这篇文章是来给大家普及Jenkins知识的, Jenkins能解决什么问题, 有哪些应用场景, 为何要掌握Jenkins, 掌握Jenkins后有哪些好处, 弄懂Jenkins需要掌握哪些知识 不知道 ...

  6. js原型和原型链理解 constructor 构造函数

    一.对象:普通对象   函数对象 二.构造函数特点:1.需要new实例化,内部使用this对象指向即将要生成的实例对象  2.首字母大写,用于区分普通函数 function Person(name){ ...

  7. 对帧率、I/P率、I帧间隔的理解

    码率就是数据传输时单位时间传送的数据位数,一般我们用的单位是kbps即千位每秒.通俗一点的理解就是取样率,单位时间内取样率越大,精度就越高,处理出来的文件就越接近原始文件,但是文件体积与取样率是成正比 ...

  8. genymotion启动模拟器后,sdk查询adb devices为空-解决方案

    我们在genymotion中安装了一个安卓模拟器,比如Google Nexus 4,启动该模拟器后,在cmd中输入adb devices,发现为空. 解决方案:在genymotion选择Setting ...

  9. P7294-[USACO21JAN]Minimum Cost Paths P【单调栈】

    正题 题目链接:https://www.luogu.com.cn/problem/P7294 题目大意 \(n\times m\)的网格,当你在\((x,y)\)时你有两种选择 花费\(x^2\)的代 ...

  10. CF757F-Team Rocket Rises Again【最短路,DAG支配树】

    正题 题目链接:https://www.luogu.com.cn/problem/CF757F 题目大意 \(n\)个点\(m\)条边的一张无向图,求删除\(s\)以外的一个点改变\(s\)到最多点的 ...