Go channel系列:html
誰也沒法保證某些狀況下的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中。
上面有兩個注意點:
<-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()。
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秒。