博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Go基础系列:为select设置超时时间
阅读量:6443 次
发布时间:2019-06-23

本文共 3745 字,大约阅读时间需要 12 分钟。

Go channel系列

After()

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

time.After()的定义如下:

func After(d Duration) <-chan Time

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

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

看下面的示例:

package mainimport (    "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.0019948012018-11-20 19:05:12.5496378 +0800 CST m=+1.0076019010xc042052060

如果将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 mainimport (    "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 mainimport (    "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: 91 second over: 101 second over: 111 second over: 121 second over: 131 second over: 141 second over: 151 second over: 167 second over: 16

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

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

1 second over: 167 second over: 16

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

转载地址:http://jppwo.baihongyu.com/

你可能感兴趣的文章
新博客
查看>>
遗传算法的基本原理与方法--笔记<转>
查看>>
《人月神话》读书笔记1
查看>>
三菱plc输出指示灯不亮怎么办(转载)
查看>>
Codeforces Continued Fractions
查看>>
doc2vec使用说明(一)gensim工具包TaggedLineDocument
查看>>
Mac下利用safari调试 Cordova的WebApp
查看>>
App测试中ios和Android的区别
查看>>
linux下性能分析命令[总结]
查看>>
java.lang.NullPointerException&com.cb.action.LoginAction.execute(LoginAction.java:48)
查看>>
IIS HTTP 500 内部服务器错误 服务器无法加载应用程序 '/LM/W3SVC''/LM/W3SVC' '找不到指定的元数据 【转载】...
查看>>
【转】TextView长按复制实现方法小结
查看>>
移动web两款滑屏框架介绍
查看>>
理解Docker :Docker 网络
查看>>
java int和String类型之间的相互转换
查看>>
通过Application存取公共数据比如登录信息等..
查看>>
U盘中毒了?教你如何删除System Volume Information这个顽固文件夹
查看>>
intellij maven配置与使用
查看>>
CentOS安装ntfs-3g
查看>>
SpringMVC文件下载与JSON格式
查看>>