前言

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

    index.html <div class="nav"> <ul> <li> <a href="#">Java& ...

  2. ClientValidationFunction

    CustomValidator.ClientValidationFunction 属性 获取或设置用于验证的自定义客户端脚本函数的名称. 命名空间:   System.Web.UI.WebContro ...

  3. Hash值和位运算

    一.Hash 1.md5是hash算法,不可逆,还原的是暴力穷举的方式解析的:加盐之后穷举也不能还原: 2.压缩映射会有重复,即哈希冲突: 二.ConcurrentHashMap 1.putIfAbs ...

  4. 多Host情况下IDEA无法启动Tomcat的问题

    学习Java Web,学到将WAR包部署到Tomcat中时,遇到一个问题. 部署WAR包的过程本身没什么问题,把.war文件放在<Tomcat安装目录>/webapps/中,然后修改< ...

  5. Sentry Web 前端监控 - 最佳实践(官方教程)

    系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Maps Sentry For ...

  6. shell编程之条件语句

    目录: 一.条件测试 1.test命令测试 2.文件测试 3.字符串比较 4.逻辑测试 二.if语句 1.if单分支语句 2.if双分支语句 3.if多分支语句 三.case语句 case多分支语句 ...

  7. MYSQL order by 排序的一个小问题探究

    小问题发现: select * from `sql` where id=1 order by (select 1 union select 2) 正常返回结果 mysql> select * f ...

  8. Docker安装GitLab与Runner(网关),常规设置,自动化用到k8s+token

    [转]图文详解k8s自动化持续集成之GitLab CI/CD Windows里面使用Debian命令行工具完成 和Docker网络相关的命令 查看某一个容器的网络 docker inspect 容器I ...

  9. 跨 Docker 宿主机 macvlan 类型

    跨 Docker 宿主机 macvlan 类型 前言 a. 本文主要为 Docker的视频教程 笔记. b. 环境为 三台 CentOS 7.0 虚拟机 (Vmware Workstation 15 ...

  10. PHP执行数据库定时备份 和手动还原

    一 备份数据库 我的这个是在TP5上,其实不在TP5也可以 逻辑: 1 首先在自己电脑的cmd命令上测试备份数据库,成功才能往下进行所以得到 C:/luanxiede/mysql-5.7/bin/my ...