Golang定時器斷續器

定時器

1.定時器結構函數

  • 結構定義測試

    type Timer struct {
        C <-chan Time       // 接受定時器事件的通道
        r runtimeTimer
    }
    
    type runtimeTimer struct {
        tb uintptr
        i  int
    
        when   int64
        period int64
        f      func(interface{}, uintptr) // NOTE: must not be closure
        arg    interface{}
        seq    uintptr
    }

2.建立定時器ui

  • 接口定義.net

    func NewTimer(d Duration) *Timer
  • 使用簡單實例調試

    var timer = NewTimer(time.Second)
    
    go func() {
        for {
            select {
            case <-timer.C:
                fmt.Println("time out.")
            }
        }
    }()
  • NewTimer源代碼:code

    func NewTimer(d Duration) *Timer {
        c := make(chan Time, 1)     // 建立一個帶有一個Time結構緩衝的通道
        t := &Timer{
            C: c,
            r: runtimeTimer{        // 運行時定時器
                when: when(d),      // 定時多久
                f:    sendTime,     // Golang寫入時間的回調接口
                arg:  c,            // 往哪一個通道寫入時間
            },
        }
        startTimer(&t.r)            // 啓動提交定時器
        return t
    }
    
    // 時間到後,Golang自動調用sendTime接口,嘗試往c通道寫入時間
    func sendTime(c interface{}, seq uintptr) {
        // 給c通道以非阻塞方式發送時間
        // 若是被用於NewTimer, 不管如何不能阻塞.
        // 若是被用於NewTicker,接收方未及時接受時間,則會丟棄掉,由於發送時間是週期性的。
        select {
        case c.(chan Time) <- Now():
        default:
        }
    }
    
    func startTimer(*runtimeTimer)
  • 代碼實例協程

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
    
        // 建立延遲3s的定時器
        exit := make(chan bool)
        timer := time.NewTimer(3 * time.Second)
    
        go func() {
            defer func() {
                exit <- true
            }()
    
            select {
            case <-timer.C:
                fmt.Println("time out.")
                return
            }
        }()
    
        <-exit
    }

3.中止定時器blog

  • 接口定義接口

    func (t *Timer) Stop() bool
    • 本接口能夠防止計時器出觸發。若是定時器中止,則返回true,若是定時器已過時或已中止,則返回false。
      Stop不關閉通道,以防止通道讀取的操做不正確。事件

    • 爲防止經過NewTimer建立的定時器,在調用Stop接口後觸發,檢查Stop返回值並清空通道。如:

      if !t.Stop() {
          <-t.C
      }

      但不能與Timer的通道中的其餘接受同時進行!!!

    • 對於使用AfterFunc(d, f)建立的定時器,若是t.Stop()返回false,則定時器已通過期,而且函數f已經在本身的協程中啓動。
      不管函數f是否執行完成,Stop()返回不會阻塞,若是用戶須要知道f是否執行完畢,必須明確地與f協調。

  • 內部使用接口

    func stopTimer(*runtimeTimer) bool
  • 代碼實例

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        timer := time.NewTimer(time.Second)
        time.Sleep(time.Millisecond * 500)
        timer.Stop()
    
        fmt.Println("timer stopped")
        time.Sleep(time.Second * 3)
    }

4.重置定時器

  • 接口定義

    func (t *Timer) Reset(d Duration) bool
    • 定時器被激活,返回true,若定時器已過時或已被中止,返回false。

    • 若程序已從t.C中接受數據,定時器已知過時,t.Rest()可直接使用

    • 若程序還還沒有從t.C收到一個值,則必須中止定時器。若是Stop提示計時器在中止以前已過時,則應明確清空通道。
      go if !t.Stop() { <-t.c } t.Rest(d)
  • 代碼實例

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func doTimer(t *time.Timer, exit chan<- bool) {
    
        go func(t *time.Timer) {
            defer func() {
                exit <- true
            }()
    
            for {
                select {
                case c := <-t.C:
                    fmt.Println("timer timeout at", c)
                    return
                }defer func() {
            ticker.Stop()
            fmt.Println("ticker stopped")
        } ()
            }
        }(t)
    }
    
    func main() {
        sign := make(chan bool)
        timer := time.NewTimer(time.Second * 3)
    
        doTimer(timer, sign)
        time.Sleep(time.Second)
    
        // 實際測試:註釋下面三行代碼,效果同樣。
        if !timer.Stop() {
            <-timer.C
        }
    
        timer.Reset(time.Second * 3)
        fmt.Println("timer reset at", time.Now())
    
        <-sign
    }

5.After接口

  • 接口定義

    func After(d Duration) <-chan Time
    • time.After函數,表示多少時間,寫入當前時間,在取出channel時間以前不會阻塞,後續程序能夠繼續執行
    • time.After函數,一般用於處理程序超時問題

    • 等待一段時間d後,Golang會發送當前時間到返回的通道上。
    • 底層的定時器不會被GC回收,若是考慮效率,可以使用NewTimer建立定時器,若是不須要,則調用Timer.Stop

  • 源碼實例

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        sign := make(chan bool)
        chan1 := make(chan int)
        chan2 := make(chan int)
    
        defer func() {
            close(sign)
            close(chan1)
            close(chan2)
        }()
    
        go func() {
            for {
                select {
                case c := <-time.After(time.Second * 3):
                    fmt.Println("After at", c)
                    // 若不往sign通道寫入數據,程序循環每隔3s執行當前case分支。
                    sign <- true
                case c1 := <-chan1:
                    fmt.Println("c1", c1)
                case c2 := <-chan2:
                    fmt.Println("c1", c2)
                }
            }
        }()
    
        <-sign
    }

6.AfterFun接口

  • 接口定義

    func AfterFunc(d Duration, f func()) *Timer
    • 等待一段時間d後,Golang會在本身的協程中調用f。並返回一個定時器,可使用Stop方法取消調用
  • 代碼實例

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
    
        timer := time.AfterFunc(time.Second*3, func() {
            fmt.Println("AfterFunc Callback")
        })
    
        time.Sleep(time.Second * 5)
        timer.Stop()
    }

斷續器

  • 斷續器(滴答器)持有一個通道,該通道每隔一段時間發送時鐘的滴答

  • 注1:從已經關閉的斷續器中讀取數據發生報錯。因此在退出處理斷續器流程前,須要先取消斷續器。

  • 注2:通過取消的斷續器,不能再複用,須要從新建立一個新的斷續器。

  • 結構定義以下:

    type Ticker struct {
        C <-chan Time   // The channel on which the ticks are delivered.
        r runtimeTimer
    }
    
    type runtimeTimer struct {
        tb uintptr
        i  int
    
        when   int64
        period int64
        f      func(interface{}, uintptr) // NOTE: must not be closure
        arg    interface{}
        seq    uintptr
    }
  • 初始化斷續器

    var ticker = time.NewTicker(time.Second)
  • 取消斷續器

    var ticker = time.NewTicker(time.Second)
    ticker.Stop()

實例一:使用Ticker(並使用時間控制ticker)

  • 代碼以下:

    package main
    import (
        "fmt"
        "time"
    )
    
    func TickerTest() *time.Ticker {
        // 建立一個斷續器
        var ticker = time.NewTicker(time.Second)
    
        go func() {
            // 使用for + range組合處理斷續器
            for t := range ticker.C {
                fmt.Println("tick at", t)
            }
        }()
    
        return ticker
    }
    
    func main() {
        ticker := TickerTest()
        time.Sleep(time.Second * 10)
        ticker.Stop()        
    }

實例二:使用channel控制ticker

  • 參考連接:https://blog.csdn.net/yjp19871013/article/details/82048944

  • 代碼以下:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func DoTicker(ticker *time.Ticker) chan<- bool {
        stopChan := make(chan bool)
    
        go func(ticker *time.Ticker) {
            // 註冊中止ticker方法
            defer ticker.Stop()
            for {
                select {
                // 處理斷續器事件
                case t := <-ticker.C:
                    fmt.Println("tick at", t)
                // 接受外部中止斷續器事件
                case stop := <-stopChan:
                    if stop {
                        fmt.Println("DoTicker Exit")
                        return
                    }
                }
            }
        }(ticker)
    
        // 返回由外部控制Ticker中止的Channel
        return stopChan
    }
    
    func main() {
    
        var ticker = time.NewTicker(time.Second)
        stopChan := DoTicker(ticker)
    
        time.Sleep(time.Second * 10)
        // 中止斷續器
        stopChan <- true
        time.Sleep(time.Second) 
        close(stopChan)
    }

實例三:使用channel控制中止ticker

  • 參考連接:https://www.kancloud.cn/digest/batu-go/153534

  • 代碼以下:

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func DoTicker(ticker *time.Ticker, times int) {
        // 建立有times個緩衝的byte通道
        stopChan := make(chan byte, times)
    
        go func(ticker *time.Ticker) {
    
            defer func() {
                // 通過調試,defer語句塊並未執行
                ticker.Stop()
                fmt.Println("ticker stopped")
            } ()
    
            for t := range ticker.C {
                fmt.Println("write stop channel")
    
                // 寫滿times次後,當前goroutine自動退出
                stopChan <- 0
                fmt.Println("tick at", t)
            }
    
            // 經調試,該語句並未執行
            fmt.Println("DoTicker1 Exit")
        }(ticker)
    }
    
    func main() {
        var ticker = time.NewTicker(time.Second)
    
        DoTicker(ticker, 5)
        time.Sleep(time.Second * 10)
    }
  • 調試輸出:

    write stop channel
    tick at 2019-03-13 11:44:35.932692894 +0800 CST m=+1.000442776
    write stop channel
    tick at 2019-03-13 11:44:36.932643384 +0800 CST m=+2.000393270
    write stop channel
    tick at 2019-03-13 11:44:37.932565147 +0800 CST m=+3.000315031
    write stop channel
    tick at 2019-03-13 11:44:38.932735589 +0800 CST m=+4.000485469
    write stop channel
    tick at 2019-03-13 11:44:39.932553565 +0800 CST m=+5.000303443
    write stop channel
    
    Process finished with exit code 0
相關文章
相關標籤/搜索