淺析GO語言中如何優雅地中斷定時任務

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

問題描述

現在我們建立了一個定時器,能定時的去做某件事,並且在執行時間逾時的時候,能把這個定時器關掉。例如需要收集一周的日誌,建立一個定時任務去收集日誌,每5秒鐘執行一次,一周的時間過後需要停掉這個定時任務。

標準庫Ticker

標準庫提供裡的Ticker類,主要功能是定時重複的去做某件事情,如果沒有設定逾時,它會一直執行下去。常見的寫法如下:

t := time.NewTicker(3 * time.Second)timeout := time.After(10 * time.Second)go func() {        for {                   <-t.C                 ...        }       }()<-timeout...

注意到這個Ticker對象是無法關閉的,好的,你可能會發現Ticker類提供了Stop方法。但是我們看看如果你這樣去關閉t的話,會出現什麼情況。

package mainimport (        "fmt"        "time")func DoTickerWork(res chan interface{}, timeout <-chan time.Time) {        t := time.NewTicker(3 * time.Second)        go func() {                defer close(res)                i := 1                for {                        <-t.C                        fmt.Printf("start %d th worker\n", i)                        res <- i                        i++                }        }()        <-timeout        t.Stop()        return}func main() {        res := make(chan interface{}, 10000)        timeout := time.After(10 * time.Second)        DoTickerWork(res, timeout)        for v := range res {                fmt.Println(v)        }}

直覺上來看,新起的goroutine在等待的過程中,主線程會把定時器關掉,似乎沒有什麼bug,然而輸出是這樣:

$go run ticker.go start 1 th workerstart 2 th workerstart 3 th worker123fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:main.main()    /home/gepin.zs/go/src/timer/ticker.go:29 +0xadgoroutine 6 [chan receive]:main.DoTickerWork.func1(0xc42006c060, 0xc4200161c0)    /home/gepin.zs/go/src/timer/ticker.go:14 +0x8ecreated by main.DoTickerWork    /home/gepin.zs/go/src/timer/ticker.go:19 +0x60exit status 2

這說明Ticker對象的stop方法並沒有關掉這個Ticker的channel,而只是阻止了channel的資料寫入,所以goroutine的任務依然在進行中,但是<-t.C一直阻塞,出現了deadlock的情況。可能會有人說調用close(t.C)就可以了,但是編譯會報錯:cannot close receive-only channel, 因為t.C是一個唯讀隊列,無法調用close方法。

怎麼解決

不要以為stop就可以關掉Ticker了,我們可以建立一個名字為done的channel,緩衝大小為1,goroutine裡面採用select,然後嘗試擷取timeout,如果能夠取到,說明已經觸發逾時,然後close(done),這個時候任務結束,主線程return。代碼如下:

package mainimport (        "fmt"        "time")func DoTickerWork(res chan interface{}, timeout <-chan time.Time) {        t := time.NewTicker(3 * time.Second)        done := make(chan bool, 1)        go func() {                defer close(res)                i := 1                for {                        select {                        case <-t.C:                                fmt.Printf("start %d th worker\n", i)                                res <- i                                i++                        case <-timeout:                                close(done)                                return                        }                }        }()        <-done        return}func main() {        res := make(chan interface{}, 10000)        timeout := time.After(10 * time.Second)        DoTickerWork(res, timeout)        for v := range res {                fmt.Println(v)        }}

程式返回結果

$go run ticker.gostart 1 th workerstart 2 th workerstart 3 th worker123
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.