├── .travis.yml ├── LICENSE ├── README.md ├── go.mod ├── goTimeWheel.png ├── tw.go └── tw_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.12.9 4 | 5 | script: 6 | - go vet 7 | - go test -v -race -coverprofile=coverage.txt -covermode=atomic 8 | 9 | after_success: 10 | - bash <(curl -s https://codecov.io/bash) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 zheng-ji.info 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## goTimeWheel 2 | 3 | [![Build Status](https://travis-ci.org/zheng-ji/goTimeWheel.svg)](https://travis-ci.org/zheng-ji/goTimeWheel) 4 | [![codecov](https://codecov.io/gh/zheng-ji/goTimeWheel/branch/master/graph/badge.svg)](https://codecov.io/gh/zheng-ji/goTimeWheel) 5 | [![GoDoc](https://godoc.org/github.com/zheng-ji/goTimeWheele?status.svg)](https://godoc.org/github.com/zheng-ji/goTimeWheel) 6 | 7 | TimeWheel Implemented By Go. 8 | Go 实现的时间轮,俗称定时器 [Link](http://zheng-ji.info/2019/10/11/2019-10-11-timewheel/) 9 | 10 | ![goTimeWheel](https://github.com/zheng-ji/goTimeWheel/blob/master/goTimeWheel.png) 11 | 12 | Feature 13 | -------- 14 | 15 | * Effective at Space Usage 16 | * Each Timer Can Custom Its Task 17 | 18 | 19 | Installation 20 | ------------- 21 | 22 | ``` 23 | go get github.com/zheng-ji/goTimeWheel 24 | ``` 25 | 26 | Example 27 | ------- 28 | 29 | ```go 30 | import ( 31 | "fmt" 32 | "github.com/zheng-ji/goTimeWheel" 33 | ) 34 | 35 | func main() { 36 | // timer ticker 37 | tw := goTimeWheel.New(1*time.Second, 3600) 38 | tw.Start() 39 | 40 | // "ID1" means the timer's name 41 | // Specify a function and params, it will run after 3s later 42 | name := "ID1" 43 | params := map[string]int{"age": 1} 44 | fn := func(data interface{}) { 45 | fmt.Printf("hello, %v\n", data) 46 | } 47 | tw.AddTimer(3*time.Second, name, fn, params) 48 | 49 | // Your Logic Code 50 | select{} 51 | 52 | } 53 | ``` 54 | 55 | License 56 | ------- 57 | 58 | Copyright (c) 2019 by [zheng-ji](http://zheng-ji.info) released under MIT License. 59 | 60 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zheng-ji/goTimeWheel 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /goTimeWheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zheng-ji/goTimeWheel/eaec46e2238a8bc716c33169bbaab282e7c76e66/goTimeWheel.png -------------------------------------------------------------------------------- /tw.go: -------------------------------------------------------------------------------- 1 | package goTimeWheel 2 | 3 | import ( 4 | "container/list" 5 | "time" 6 | ) 7 | 8 | // TimeWheel Struct 9 | type TimeWheel struct { 10 | interval time.Duration // ticker run interval 11 | 12 | ticker *time.Ticker 13 | 14 | slots []*list.List 15 | 16 | keyPosMap map[interface{}]int // keep each timer's postion 17 | 18 | slotNum int 19 | currPos int // timewheel current postion 20 | 21 | addChannel chan Task // channel to add Task 22 | removeChannel chan interface{} // channel to remove Task 23 | stopChannel chan bool // stop signal 24 | } 25 | 26 | // Task Struct 27 | type Task struct { 28 | key interface{} // Timer Task ID 29 | 30 | delay time.Duration // Run after delay 31 | circle int // when circle equal 0 will trigger 32 | 33 | fn func(interface{}) // custom function 34 | params interface{} // custom parms 35 | } 36 | 37 | // New Func: Generate TimeWheel with ticker and slotNum 38 | func New(interval time.Duration, slotNum int) *TimeWheel { 39 | 40 | if interval <= 0 || slotNum <= 0 { 41 | return nil 42 | } 43 | 44 | tw := &TimeWheel{ 45 | interval: interval, 46 | slots: make([]*list.List, slotNum), 47 | keyPosMap: make(map[interface{}]int), 48 | currPos: 0, 49 | slotNum: slotNum, 50 | addChannel: make(chan Task), 51 | removeChannel: make(chan interface{}), 52 | stopChannel: make(chan bool), 53 | } 54 | 55 | for i := 0; i < slotNum; i++ { 56 | tw.slots[i] = list.New() 57 | } 58 | 59 | return tw 60 | } 61 | 62 | // Start Func: start ticker and monitor channel 63 | func (tw *TimeWheel) Start() { 64 | tw.ticker = time.NewTicker(tw.interval) 65 | go tw.start() 66 | } 67 | 68 | func (tw *TimeWheel) start() { 69 | for { 70 | select { 71 | case <-tw.ticker.C: 72 | tw.handle() 73 | case task := <-tw.addChannel: 74 | tw.addTask(&task) 75 | case key := <-tw.removeChannel: 76 | tw.removeTask(key) 77 | case <-tw.stopChannel: 78 | tw.ticker.Stop() 79 | return 80 | } 81 | } 82 | } 83 | 84 | func (tw *TimeWheel) Stop() { 85 | tw.stopChannel <- true 86 | } 87 | 88 | func (tw *TimeWheel) AddTimer(delay time.Duration, key interface{}, fn func(interface{}), params interface{}) { 89 | if delay < 0 { 90 | return 91 | } 92 | tw.addChannel <- Task{delay: delay, key: key, fn: fn, params: params} 93 | } 94 | 95 | func (tw *TimeWheel) RemoveTimer(key interface{}) { 96 | if key == nil { 97 | return 98 | } 99 | tw.removeChannel <- key 100 | } 101 | 102 | // handle Func: Do currPosition slots Task 103 | func (tw *TimeWheel) handle() { 104 | l := tw.slots[tw.currPos] 105 | 106 | for e := l.Front(); e != nil; { 107 | curElement := e 108 | task := e.Value.(*Task) 109 | next := e.Next() 110 | e = next 111 | if task.circle > 0 { 112 | task.circle-- 113 | continue 114 | } 115 | 116 | go task.fn(task.params) 117 | 118 | l.Remove(curElement) 119 | if task.key != nil { 120 | delete(tw.keyPosMap, task.key) 121 | } 122 | } 123 | 124 | tw.currPos = (tw.currPos + 1) % tw.slotNum 125 | } 126 | 127 | // getPosAndCircle Func: parse duration by interval to get circle and position 128 | func (tw *TimeWheel) getPosAndCircle(d time.Duration) (pos int, circle int) { 129 | circle = int(d.Seconds()) / int(tw.interval.Seconds()) / tw.slotNum 130 | 131 | pos = (tw.currPos + int(d.Seconds())/int(tw.interval.Seconds())) % tw.slotNum 132 | return 133 | } 134 | 135 | func (tw *TimeWheel) addTask(task *Task) { 136 | pos, circle := tw.getPosAndCircle(task.delay) 137 | task.circle = circle 138 | 139 | tw.slots[pos].PushBack(task) 140 | if task.key != nil { 141 | tw.keyPosMap[task.key] = pos 142 | } 143 | } 144 | 145 | func (tw *TimeWheel) removeTask(key interface{}) { 146 | pos, ok := tw.keyPosMap[key] 147 | if !ok { 148 | return 149 | } 150 | 151 | l := tw.slots[pos] 152 | 153 | for e := l.Front(); e != nil; { 154 | task := e.Value.(*Task) 155 | if task.key == key { 156 | delete(tw.keyPosMap, task.key) 157 | l.Remove(e) 158 | } 159 | 160 | e = e.Next() 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /tw_test.go: -------------------------------------------------------------------------------- 1 | package goTimeWheel 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestAddTask(t *testing.T) { 10 | 11 | tw := New(1*time.Second, 3600) 12 | 13 | tw.Start() 14 | 15 | name := "ID1" 16 | params := map[string]int{"age": 1} 17 | fn := func(data interface{}) { 18 | fmt.Printf("hello, %v\n", data) 19 | } 20 | tw.AddTimer(3*time.Second, name, fn, params) 21 | 22 | time.Sleep(time.Duration(5) * time.Second) 23 | } 24 | 25 | func TestRmTask(t *testing.T) { 26 | 27 | tw := New(1*time.Second, 3600) 28 | 29 | tw.Start() 30 | 31 | tw.AddTimer(3*time.Second, "key1", func(data interface{}) { 32 | fmt.Printf("hello, %v\n", data) 33 | }, map[string]int{"age": 1}) 34 | 35 | tw.RemoveTimer("key1") 36 | 37 | time.Sleep(time.Duration(5) * time.Second) 38 | } 39 | 40 | func TestStopTimeWheel(t *testing.T) { 41 | 42 | tw := New(1*time.Second, 3600) 43 | 44 | tw.Start() 45 | 46 | tw.AddTimer(3*time.Second, "key1", func(data interface{}) { 47 | fmt.Printf("hello, %v\n", data) 48 | }, map[string]int{"age": 1}) 49 | 50 | tw.Stop() 51 | 52 | time.Sleep(time.Duration(5) * time.Second) 53 | } 54 | 55 | func TestBigDelay(t *testing.T) { 56 | tw := New(2*time.Second, 2) 57 | taskDelay := 7 * time.Second 58 | tw.Start() 59 | begin := time.Now() 60 | name := "ID1" 61 | params := map[string]int{"age": 1} 62 | fn := func(data interface{}) { 63 | fmt.Printf("hello, %v\n", data) 64 | cost := int(time.Now().Sub(begin).Seconds()) 65 | if cost != 8 { 66 | t.Fail() 67 | } 68 | fmt.Println("cso = ", cost) 69 | } 70 | tw.AddTimer(taskDelay, name, fn, params) 71 | 72 | time.Sleep(time.Duration(20) * time.Second) 73 | 74 | } 75 | --------------------------------------------------------------------------------