Go基礎系列:爲select設置超時時間

Go channel系列html

After()

誰也沒法保證某些狀況下的select是否會永久阻塞。不少時候都須要設置一下select的超時時間,能夠藉助time包的After()實現。函數

time.After()的定義以下:指針

func After(d Duration) <-chan Time

After()函數接受一個時長d,而後它After()等待d時長,等待時間到後,將等待完成時所處時間點寫入到channel中並返回這個只讀channel。code

因此,將該函數賦值給一個變量時,這個變量是一個只讀channel,而channel是一個指針類型的數據,因此它是一個指針。htm

看下面的示例:blog

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在循環中)。get

如下是一個示例:string

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秒以後輸出:io

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秒。

相關文章
相關標籤/搜索