├── .gitignore ├── LICENSE ├── README.md ├── timewheel.go └── timewheel.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | node_modules 27 | package.json 28 | 29 | .idea 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 qiang.ou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # timewheel 2 | Golang实现的时间轮 3 | 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/ouqiang/timewheel)](https://goreportcard.com/report/github.com/ouqiang/timewheel) 5 | 6 | ![时间轮](https://raw.githubusercontent.com/ouqiang/timewheel/master/timewheel.jpg) 7 | 8 | # 安装 9 | 10 | ```shell 11 | go get -u github.com/ouqiang/timewheel 12 | ``` 13 | 14 | # 使用 15 | 16 | ```go 17 | package main 18 | 19 | import ( 20 | "github.com/ouqiang/timewheel" 21 | "time" 22 | ) 23 | 24 | func main() { 25 | // 初始化时间轮 26 | // 第一个参数为tick刻度, 即时间轮多久转动一次 27 | // 第二个参数为时间轮槽slot数量 28 | // 第三个参数为回调函数 29 | tw := timewheel.New(1 * time.Second, 3600, func(data interface{}) { 30 | // do something 31 | }) 32 | 33 | // 启动时间轮 34 | tw.Start() 35 | 36 | // 添加定时器 37 | // 第一个参数为延迟时间 38 | // 第二个参数为定时器唯一标识, 删除定时器需传递此参数 39 | // 第三个参数为用户自定义数据, 此参数将会传递给回调函数, 类型为interface{} 40 | tw.AddTimer(5 * time.Second, conn, map[string]int{"uid" : 105626}) 41 | 42 | // 删除定时器, 参数为添加定时器传递的唯一标识 43 | tw.RemoveTimer(conn) 44 | 45 | // 停止时间轮 46 | tw.Stop() 47 | 48 | select{} 49 | } 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /timewheel.go: -------------------------------------------------------------------------------- 1 | package timewheel 2 | 3 | import ( 4 | "container/list" 5 | "time" 6 | ) 7 | 8 | // @author qiang.ou 9 | 10 | // Job 延时任务回调函数 11 | type Job func(interface{}) 12 | // TaskData 回调函数参数类型 13 | 14 | // TimeWheel 时间轮 15 | type TimeWheel struct { 16 | interval time.Duration // 指针每隔多久往前移动一格 17 | ticker *time.Ticker 18 | slots []*list.List // 时间轮槽 19 | // key: 定时器唯一标识 value: 定时器所在的槽, 主要用于删除定时器, 不会出现并发读写,不加锁直接访问 20 | timer map[interface{}]int 21 | currentPos int // 当前指针指向哪一个槽 22 | slotNum int // 槽数量 23 | job Job // 定时器回调函数 24 | addTaskChannel chan Task // 新增任务channel 25 | removeTaskChannel chan interface{} // 删除任务channel 26 | stopChannel chan bool // 停止定时器channel 27 | } 28 | 29 | // Task 延时任务 30 | type Task struct { 31 | delay time.Duration // 延迟时间 32 | circle int // 时间轮需要转动几圈 33 | key interface{} // 定时器唯一标识, 用于删除定时器 34 | data interface{} // 回调函数参数 35 | } 36 | 37 | // New 创建时间轮 38 | func New(interval time.Duration, slotNum int, job Job) *TimeWheel { 39 | if interval <= 0 || slotNum <= 0 || job == nil { 40 | return nil 41 | } 42 | tw := &TimeWheel{ 43 | interval: interval, 44 | slots: make([]*list.List, slotNum), 45 | timer: make(map[interface{}]int), 46 | currentPos: 0, 47 | job: job, 48 | slotNum: slotNum, 49 | addTaskChannel: make(chan Task), 50 | removeTaskChannel: make(chan interface{}), 51 | stopChannel: make(chan bool), 52 | } 53 | 54 | tw.initSlots() 55 | 56 | return tw 57 | } 58 | 59 | // 初始化槽,每个槽指向一个双向链表 60 | func (tw *TimeWheel) initSlots() { 61 | for i := 0; i < tw.slotNum; i++ { 62 | tw.slots[i] = list.New() 63 | } 64 | } 65 | 66 | // Start 启动时间轮 67 | func (tw *TimeWheel) Start() { 68 | tw.ticker = time.NewTicker(tw.interval) 69 | go tw.start() 70 | } 71 | 72 | // Stop 停止时间轮 73 | func (tw *TimeWheel) Stop() { 74 | tw.stopChannel <- true 75 | } 76 | 77 | // AddTimer 添加定时器 key为定时器唯一标识 78 | func (tw *TimeWheel) AddTimer(delay time.Duration, key interface{}, data interface{}) { 79 | if delay < 0 { 80 | return 81 | } 82 | tw.addTaskChannel <- Task{delay: delay, key: key, data: data} 83 | } 84 | 85 | // RemoveTimer 删除定时器 key为添加定时器时传递的定时器唯一标识 86 | func (tw *TimeWheel) RemoveTimer(key interface{}) { 87 | if key == nil { 88 | return 89 | } 90 | tw.removeTaskChannel <- key 91 | } 92 | 93 | func (tw *TimeWheel) start() { 94 | for { 95 | select { 96 | case <-tw.ticker.C: 97 | tw.tickHandler() 98 | case task := <-tw.addTaskChannel: 99 | tw.addTask(&task) 100 | case key := <-tw.removeTaskChannel: 101 | tw.removeTask(key) 102 | case <-tw.stopChannel: 103 | tw.ticker.Stop() 104 | return 105 | } 106 | } 107 | } 108 | 109 | func (tw *TimeWheel) tickHandler() { 110 | l := tw.slots[tw.currentPos] 111 | tw.scanAndRunTask(l) 112 | if tw.currentPos == tw.slotNum-1 { 113 | tw.currentPos = 0 114 | } else { 115 | tw.currentPos++ 116 | } 117 | } 118 | 119 | // 扫描链表中过期定时器, 并执行回调函数 120 | func (tw *TimeWheel) scanAndRunTask(l *list.List) { 121 | for e := l.Front(); e != nil; { 122 | task := e.Value.(*Task) 123 | if task.circle > 0 { 124 | task.circle-- 125 | e = e.Next() 126 | continue 127 | } 128 | 129 | go tw.job(task.data) 130 | next := e.Next() 131 | l.Remove(e) 132 | if task.key != nil { 133 | delete(tw.timer, task.key) 134 | } 135 | e = next 136 | } 137 | } 138 | 139 | // 新增任务到链表中 140 | func (tw *TimeWheel) addTask(task *Task) { 141 | pos, circle := tw.getPositionAndCircle(task.delay) 142 | task.circle = circle 143 | 144 | tw.slots[pos].PushBack(task) 145 | 146 | if task.key != nil { 147 | tw.timer[task.key] = pos 148 | } 149 | } 150 | 151 | // 获取定时器在槽中的位置, 时间轮需要转动的圈数 152 | func (tw *TimeWheel) getPositionAndCircle(d time.Duration) (pos int, circle int) { 153 | delaySeconds := int(d.Seconds()) 154 | intervalSeconds := int(tw.interval.Seconds()) 155 | circle = int(delaySeconds / intervalSeconds / tw.slotNum) 156 | pos = int(tw.currentPos+delaySeconds/intervalSeconds) % tw.slotNum 157 | 158 | return 159 | } 160 | 161 | // 从链表中删除任务 162 | func (tw *TimeWheel) removeTask(key interface{}) { 163 | // 获取定时器所在的槽 164 | position, ok := tw.timer[key] 165 | if !ok { 166 | return 167 | } 168 | // 获取槽指向的链表 169 | l := tw.slots[position] 170 | for e := l.Front(); e != nil; { 171 | task := e.Value.(*Task) 172 | if task.key == key { 173 | delete(tw.timer, task.key) 174 | l.Remove(e) 175 | } 176 | 177 | e = e.Next() 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /timewheel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ouqiang/timewheel/7da73bb634e5ad869d0f1e0577d36d877edaad0f/timewheel.jpg --------------------------------------------------------------------------------