這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
本人最近新學GO,為了能夠深入瞭解GO,於是計劃使用GO實現一個高並發、IO密集操作的ETL程式。閑話不多說,今天完成了ETL中最基本的要素,定時器。
首先瞭解到GO中的time.Ticker不適用到高精度的定時器,因為ETL任務周期設定是1分鐘,故此處無需高精度的定時器(對於高精度的定時器,在網上看到有人使用時間輪實現timewheel,我也看了實現,並且也按其思路實現了一版,此處略去)。
封閉的類型:TimeTicker,使用範例程式碼如下:
func main() {
var tt *timeticker.TimeTicker = timeticker.New(3*time.Second) //建立一個3秒鐘執行一次任務的定時器
e := tt.AddJob(job, true) //向此定時器中增加一個任務,返回此任務的指標(此指標在後續刪除任務時使用)
go tt.Start() //開一個協程,讓此定時器單獨去執行任務
time.Sleep(6*time.Second)
//主線程睡6秒後再加一個任務到定時器的任務隊列,之後每周期會執行兩個任務
tt.AddJob(test_job, true)
time.Sleep(10*time.Second)
tt.RemoveTask(e) //主線程睡10秒後,清除掉第一個job,任務隊列就只剩一個任務了
time.Sleep(10*time.Second)
tt.Stop() //停止此定時器,此後會發現定時器不再執行
time.Sleep(5*time.Second) //睡五秒後退出
}
func test_job(...interface{}) {
fmt.Print("test_job is call ...")
fmt.Println(time.Now())
}
func job(...interface{}) {
fmt.Print("job is call ...")
fmt.Println(time.Now())
}
程式執行結果:
job is call ...2018-04-11 22:21:42.0179853 +0800 CST m=+0.005000301
job is call ...2018-04-11 22:21:45.0181569 +0800 CST m=+3.005171901
test_job is call ...2018-04-11 22:21:48.0183285 +0800 CST m=+6.005343501
job is call ...2018-04-11 22:21:48.0183285 +0800 CST m=+6.005343501
job is call ...2018-04-11 22:21:51.0185001 +0800 CST m=+9.005515101
test_job is call ...2018-04-11 22:21:51.0185001 +0800 CST m=+9.005515101
test_job is call ...2018-04-11 22:21:54.0186717 +0800 CST m=+12.005686701
job is call ...2018-04-11 22:21:54.0186717 +0800 CST m=+12.005686701
test_job is call ...2018-04-11 22:21:57.0188433 +0800 CST m=+15.005858301
job is call ...2018-04-11 22:21:57.0188433 +0800 CST m=+15.005858301
test_job is call ...2018-04-11 22:22:00.0180149 +0800 CST m=+18.005029901
test_job is call ...2018-04-11 22:22:03.0181865 +0800 CST m=+21.005201501
test_job is call ...2018-04-11 22:22:06.0183581 +0800 CST m=+24.005373101
從上述運行結果可以看出,此定時器到秒級還算精準,但時間精度再小就會有偏差了,這就是開頭說的不適用於高精度的定時器。上面的使用範例程式碼中的注釋已經說明的很清楚了,下面獻上封裝的定時器的代碼(特意加了部分注釋,方便需要的同學理解):
//************************code begin********************************
package timeticker
import (
"fmt"
"time"
//"reflect"
"container/list"
)
type Task struct {
interval time.Duration //當前任務延遲多長時間後執行
job func(...interface{}) //回呼函數(任務的具體執行方法)
isCycle bool //任務是否只執行一次(true,執行一次後就會從執行隊列中清除掉
}
//定時器類型
type TimeTicker struct {
interval time.Duration //定時器間隔多長時間執行一次
ticker *time.Ticker
taskQueue *list.List
closeChan chan bool //向此通道中輸入true值,則會關閉此定時器
}
func New(interval time.Duration) *TimeTicker {
if interval <= 0 {
return nil
}
tt := &TimeTicker {
interval: interval,
taskQueue: list.New(),
closeChan: make(chan bool),
}
return tt
}
//向定時器的任務隊列中增加任務
//參數:task--增加的任務
// isCycle--是否迴圈執行, true,每次執行完後依然保留在任務隊列中,false,執行完一次後就會被清除掉
//考慮增加一組任務的方法
func (tt *TimeTicker) AddTask(task Task, isCycle bool) {
tt.taskQueue.PushBack(task)
}
func (tt *TimeTicker) AddTaskList(task Task, isCycle bool) {
}
//增加一個執行任務 參數:job--具體執行的函數
// isCycle--是否迴圈執行,true:迴圈執行,每個周期均執行一次, false:只執行一次,然後從任務隊列中清除
func (tt *TimeTicker) AddJob(job func(...interface{}), isCycle bool) *list.Element {
task := &Task {
interval: 0,
job: job,
isCycle: isCycle,
}
e := tt.taskQueue.PushBack(task)
return e
}
func (tt *TimeTicker) RemoveTask(e *list.Element) {
tt.taskQueue.Remove(e)
//fmt.Println(tt.taskQueue.Len())
}
func (tt *TimeTicker) Stop() {
tt.ticker.Stop()
tt.closeChan <- true
}
//啟動定時器,如果不傳參數表示立刻啟動,否則delay時間後啟動
func (tt *TimeTicker) Start(delay ...time.Duration) {
if len(delay) > 0 { //延遲delay[0]時間後啟動
} else { //立刻啟動
tt.ticker = time.NewTicker(tt.interval)
tt.executeTaskQueue()
for {
select {
case <- tt.ticker.C:
tt.executeTaskQueue()
case <- tt.closeChan:
return
}
}
}
}
func (tt *TimeTicker) executeTaskQueue() {
if tt.taskQueue.Len() == 0 {
fmt.Println("任務隊列為空白,沒有可執行檔任務......")
return
}
for iter := tt.taskQueue.Front(); iter != nil; iter = iter.Next() {
t := iter.Value
task,ok := t.(*Task)
if ok {
go task.job()
if !task.isCycle {
iter_tmp := iter
iter = iter.Prev()
tt.taskQueue.Remove(iter_tmp)
if iter == nil {
return
}
}
}
}
}
//設定逾時機制,當設定此函數後,定時器會從任務啟動時間開始計時,
//如果達到此時間後,任務隊列中的任務達仍然未被執行完,則中止中剩餘的任務,並發出警告
func (tt *TimeTicker) SetTimeOut(setTime time.Duration) {
}
//************************code end********************************
大部分函數前都加了很詳細的注釋,方便大家閱讀。還有一些設計好的函數並未實現,但主體功能都已經出來了,並不影響使用,且最主要的原因是ETL中沒有這些(未實現的函數)功能的需求,就偷個賴了。
338 次點擊 ∙ 1 贊