Golang多線程初識-day28-定時器(超時處理)

introduction

  • Timer可以讓用戶自定義超時的邏輯

    • 尤其是在應對select處理多個channel的超時、單channel讀寫的超時等情形,
    • 一次性的時間觸發事件,其與Ticker不同
  • Ticker是按一定時間間隔持續觸發時間事件

  • 設置超時時間(timeout),避免阻塞時間過長
  • 定時器(Ticker)位於time包中,使用NewTicker()方法生成定時器

生成定時器(Ticker)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"time"
"fmt"
)

func main(){
//使用NewTicker設置定時器,參數為時間的間隔
//以下為設置一個一秒執行一次的定時器
timer := time.NewTicker(time.Second)

//使用range()方法從timer.C管道中取出數據
for v:= range(timer.C) {
fmt.Println("hello",v)
}
}
  • timer.C 本身為一個channel,其後端有一個goroutine,每隔一段NewTicker()方法設置的時間參數,會往裡面寫入時間
  • 上述利用range()方法將數據取出

result

1
2
3
4
5
hello 2018-05-19 18:18:47.193000586 +0800 CST m=+1.004599317
hello 2018-05-19 18:18:48.190658733 +0800 CST m=+2.002270359
hello 2018-05-19 18:18:49.189746058 +0800 CST m=+3.001370599
...
...
  • 因為每隔一秒goroutine才會往timer.C管道扔數據,因此主線程也是每一秒才能從管道中取出數據

一次性定時器

  • 搭配select一起使用,仍是用作設置timeout
  • time.After(Time)的參數為一時間

    • 相當於time.NewTimer(d).C
    • 為一 只讀channel 類型
  • 其它創建方式

    1
    2
    t := time.NewTimer(d)
    t := time.AfterFunc(d,f)
    • 定時時間:d
    • 觸發後執行的動作:f
    • 時間channel:t

example 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"time"
"fmt"
)

func main(){
IntChan1 := make(chan int,1)
IntChan2 := make(chan int,2)
i := 0
for {
//time.After開始計時
select {
case IntChan1 <- i:
fmt.Printf("i value %d in Chan 1\n",i)
case IntChan2 <- i:
fmt.Printf("i value %d in Chan 2\n",i)

//滿足time.After()參數設置的時間,
//表示無法滿足上面任一case操作(兩管道皆已滿),操作已超時
//將time.After()管道中的時間丟棄,並完成select操作
case <- time.After(time.Second):
fmt.Println("cross 1 second: timeout")
}
i++
}
}

result

1
2
3
4
5
6
7
8
i value 0 in Chan 1
i value 1 in Chan 2
i value 2 in Chan 2
cross 1 second: timeout
cross 1 second: timeout
cross 1 second: timeout
...
...

tips

  • 較不推薦使用此方法作為超時控制,因為time.After()並未關閉,
  • 在for無限循環中可以看到,會創建越來越多的管道造成內存overflow

example 2

  • 使用time.NewTimer()作為一次性計時器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"time"
)

func main(){
timer := time.NewTimer(5*time.Second)

go func(){
<- timer.C
fmt.Println("開始執行函數")
}()

time.Sleep(3 * time.Second)
succ := timer.Stop() //未到timer的timeout時可停止timer的運作
if succ {
fmt.Println("timer stop succ")
return
}
fmt.Println("timer stop failed")

}

example 3

  • 使用time.NewTicker()作為一次性的計時器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"time"
"fmt"
)

func main(){
IntChan1 := make(chan int,1)
IntChan2 := make(chan int,2)

timer := time.NewTicker(time.Second)

i := 0
for {
select {
case IntChan1 <- i:
fmt.Printf("i value %d in Chan 1\n",i)
time.Sleep(time.Millisecond*800)
case IntChan2 <- i:
fmt.Printf("i value %d in Chan 2\n",i)
time.Sleep(time.Millisecond*800)

//與上個例子一樣從管道中取出時間丟棄
case <- timer.C:
fmt.Println("cross 1 second: timeout")
}
i++

//使用完一次性的計時器隨即關閉,關閉後對象就消失,在使用此對象會panic
timer.Stop()
}
}

result

1
2
3
4
i value 0 in Chan 1
i value 1 in Chan 2
i value 2 in Chan 2
fatal error: all goroutines are asleep - deadlock!
  • 第一次select已經關閉timer,但管道未滿,
  • 第四次select後,timer已關閉,再次使用發生panic

tips

推薦使用第二種方式當作一次性的定時器