Go channel系列

After()

谁也无法保证某些情况下的select是否会永久阻塞。很多时候都需要设置一下select的超时时间,可以借助time包的After()实现。

time.After()的定义如下:

func After(d Duration) <-chan Time

After()函数接受一个时长d,然后它After()等待d时长,等待时间到后,将等待完成时所处时间点写入到channel中并返回这个只读channel。

所以,将该函数赋值给一个变量时,这个变量是一个只读channel,而channel是一个指针类型的数据,所以它是一个指针。

看下面的示例:

package main

import (
"fmt"
"time"
)
func main() {
fmt.Println(time.Now())
a := time.After(1*time.Second)
fmt.Println(<-a)
fmt.Println(a)
}

输出结果:

2018-11-20 19:05:11.5440307 +0800 CST m=+0.001994801
2018-11-20 19:05:12.5496378 +0800 CST m=+1.007601901
0xc042052060

如果将After()放进select语句块的一个case中,那么就可以让其它的case有一定的时间长度来监听读、写事件,如果在这段时长内其它case还没有有可读、可写事件,这个After()所在case就会结束当前的select,然后终止select(如果select未在循环中)或进入下一轮select(如果select在循环中)。

以下是一个示例:

func main() {
ch1 := make(chan string) // 激活一个goroutine,但5秒之后才发送数据
go func() {
time.Sleep(5 * time.Second)
ch1 <- "put value into ch1"
}() select {
case val := <-ch1:
fmt.Println("recv value from ch1:",val)
return
// 只等待3秒,然后就结束
case <-time.After(3 * time.Second):
fmt.Println("3 second over, timeover")
}
}

运行后,将在大约3秒之后输出:

3 second over, timeover

上面出现了超时现象,因为新激活的goroutine首先要等待5秒,然后才将数据发送到channel ch1中。但是main goroutine继续运行到select语句块,由于第一个case未满足条件(注意,main goroutine并不会因此而阻塞)。评估第二个case时,将执行time.After()等待3秒,3秒之后读取到该函数返回的通道数据,于是该case满足select的条件,该select因为没有在循环中,所以直接结束,main goroutine也因此而终止。自始至终,新激活的goroutine都没有机会将数据发送到ch1中。

上面有两个注意点:

  • (1).3秒等待时,只有在等待完成时case才被选中,在等待过程中,select一直在评估所有的case右边的表达式
  • (2).在上面的3秒等待过程中,第一个case的评估一直在持续着,因为在等待结束之前,select还未选中任何case,而是一直在评估所有的表达式,包括<-ch1的评估。

如果将上面go func()函数的睡眠时间改为2秒,则在3秒等待时间内,第一个case的<-ch1评估满足条件,于是该case被选中,第二个case被无视。

go func() {
time.Sleep(1 * time.Second)
ch1 <- "put value into ch1"
}()

上面使用After(),也保证了select一定会选中某一个case,这时可以省略default块。

注意,After()放在select的内部和放在select的外部是完全不一样的,更助于理解的示例见下面的Tick()。

time.Tick()

After(d)是只等待一次d的时长,并在这次等待结束后将当前时间发送到通道。Tick(d)则是间隔地多次等待,每次等待d时长,并在每次间隔结束的时候将当前时间发送到通道。

因为Tick()也是在等待结束的时候发送数据到通道,所以它的返回值是一个channel,从这个channel中可读取每次等待完时的时间点。

下面是一个Tick()和After()结合的示例:

package main

import (
"fmt"
"time"
) func main() {
select {
case <-time.Tick(2 * time.Second):
fmt.Println("2 second over:", time.Now().Second())
case <-time.After(7 * time.Second):
fmt.Println("5 second over, timeover", time.Now().Second())
return
}
}

上面的示例,在等待2秒之后,就会因为读取到了time.Tick()的通道数据而终止,因为select并未在循环内。

如果select在循环内,第二个case将永远选择不到。因为每次select轮询中,第一个case都因为2秒而先被选中,使得第二个case的评估总是被中断。进入下一个select轮询后,又会重新开始评估两个case,分别等待2秒和7秒。

func main() {
for {
select {
case <-time.Tick(2 * time.Second):
fmt.Println("2 second over:", time.Now().Second())
case <-time.After(7 * time.Second):
fmt.Println("5 second over, timeover", time.Now().Second())
return
}
}
}

上面不正常执行的原因是因为每次select都会重新评估这些表达式。如果把这些表达式放在select外面,则正常:

package main

import (
"fmt"
"time"
) func main() {
tick := time.Tick(1 * time.Second)
after := time.After(7 * time.Second)
fmt.Println("start second:",time.Now().Second())
for {
select {
case <-tick:
fmt.Println("1 second over:", time.Now().Second())
case <-after:
fmt.Println("7 second over:", time.Now().Second())
return
}
}
}

返回:

start second: 9
1 second over: 10
1 second over: 11
1 second over: 12
1 second over: 13
1 second over: 14
1 second over: 15
1 second over: 16
7 second over: 16

将time.Tick()和time.After()放在for...select的外面,使得select每次只评估通道是否可读、可写事件,而不会重新执行time.Tick()和time.After(),使得它们重新进入计时状态。

注意上面的输出结果中,有两行:

1 second over: 16
7 second over: 16

说明在第16秒的时候,两个case都评估为真了,但是这一次选择了第一个case,然后进入下一个select过程,因为select的随机选择性,它会保证所有满足条件的case尽量均衡分布,这次将选择第二个case,它仍然为第16秒,这时因为一次for和select调用所花的时间不可能会超过1秒而进入第17秒。

Go基础系列:为select设置超时时间的更多相关文章

  1. 玩转Windows服务系列——Windows服务启动超时时间

    最近有客户反映,机房出现断电情况,服务器的系统重新启动后,数据库服务自启动失败.第一次遇到这种情况,为了查看是不是断电情况导致数据库文件损坏,从客户的服务器拿到数据库的日志,进行分析. 数据库工作机制 ...

  2. Mybatis设置超时时间

    Mybatis设置超时时间 mybatis如果不指定,默认超时时间是不做限制的,默认值为0.mybatis sql配置超时时间有两种方法: 1.全局配置 在mybatis配置文件的settings节点 ...

  3. FFmpeg命令读取RTMP流如何设置超时时间

    子标题:FFmpeg命令录制RTMP流为FLV文件时如何设置超时时间 | FFmpeg命令如何解决录制产生阻塞的问题0x001: 前言 今天在测试程序时遇到两个问题.Q1:ffmpeg录制RTMP流并 ...

  4. C# UdpClient 设置超时时间

    /********************************************************************** * C# UdpClient 设置超时时间 * 说明: ...

  5. mongodb3.6 (五)net 客户端访问mongodb设置超时时间踩过的“坑”

    前言 在上一篇文章中,我们有提到net访问mongodb连接超时默认为30秒,这个时间在实际项目中肯定是太长的.而MongoClientSettings 也确是提供了超时属性,如下图: 可实际使用中, ...

  6. GuzzleHttp 请求设置超时时间

    之前调用一个三方的 WEB API,大量的请求超时,导致 PHP 进程被占用完.整个网站一直报 504. 其中一个优化措施就是对三方 API 调用设置超时时间. use GuzzleHttp\Clie ...

  7. winform设置超时时间

    ); //设置超时时间 var completedTask = await Task.WhenAny(new Task(async () => { );//执行的方法示例这里用延迟代替 }), ...

  8. HttpClient 如何设置超时时间

    今天分享一个巨坑,就是 HttpClient.这玩意有多坑呢?就是每个版本都变,近日笔者深受其害. 先看一下代码,我要发送请求调用一个c++接口. public static String doPos ...

  9. 爬虫学习笔记之为什么要设置超时时间,怎么设置(使用selenium)

    一个程序没有设置超时时间,就可以说是一段有缺陷的代码. 读取超时指的就是客户端等待服务器发送请求的时间.(特定地,它指的是客户端要等待服务器发送字节之间的时间.在 99.9% 的情况下这指的是服务器发 ...

随机推荐

  1. 20145232韩文浩《网络对抗》PC平台逆向破解

    shellcode注入 1.Linux下有两种基本构造攻击buf的方法:retaddr+nop+shellcode,nop+shellcode+retaddr.我们采用anything+retaddr ...

  2. 《代码不朽:编写可维护软件的10大要则(C#版)》读后感

    本书作者Joost Visser,译者张若飞.本书讲解了编写可维护代码的10个要则,从目录就可以看出这10点分别是: 编写短小的代码单元(15行以内,在大部分情况下还是能实现的,但是当我们使用Linq ...

  3. Delphi XE7试用记录2

    Delphi XE7试用记录2 万一博客中介绍了不少Delphi7以后的新功能测试,想跟着测试一下.每次测试建立一个工程,在窗体上放几个按钮,测试几个相关的功能,这样虽然简单明了,但日后查阅起来不方便 ...

  4. 在使用可变数组过程中遇到*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'问题

    *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFD ...

  5. 自定义滚动条样式(layui.v1)

    来源于 layui css 代码 ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-button, ::-w ...

  6. JDK8 新增的日期时间API

    背景 JDK8中增加了一套全新的日期时间API,这里进行总结下,方便查询使用. 新的时间及日期API位于 java.time 包中,下面是一些关键类. Instant:代表的是时间戳. LocalDa ...

  7. Kali学习笔记15:防火墙识别、负载均衡识别、WAF识别

    防火墙简单的识别方式: 如图: 可以简单明了看出:发送SYN不回应,发送ACK回RST可以说明开启过滤等等 基于这个原理,我们可以写一个脚本来对防火墙来探测和识别: #!/usr/bin/python ...

  8. TS - 解决问题的一些方法

    How to resolve the problem? 获取基本的相关信息(后续处理问题的基础)  在怎样的背景环境下?发生了怎样的问题? 如果无法清楚地辨别或陈述问题的基本信息,那么,此时要面对的将 ...

  9. PHP之ThinkPHP框架(数据库)

    PHP是网站后台开发语言,其重要的操作对象莫过于数据库,之前有了解过mysqli和pdo,但ThinkPHP的数据库交互必须使用其特定的封装方法,或者可以认为其是对PHP数据库操作的进一步封装,以达到 ...

  10. poj 1177 --- Picture(线段树+扫描线 求矩形并的周长)

    题目链接 Description A number of rectangular posters, photographs and other pictures of the same shape a ...