├── README.md ├── bucket_test.go ├── example_scheduler_test.go ├── timingwheel_examples_test.go ├── utils.go ├── timingwheel_benchmark_test.go ├── timingwheel_test.go ├── bucket.go ├── delayqueue └── delayqueue.go └── timingwheel.go /README.md: -------------------------------------------------------------------------------- 1 | # timingwheel 2 | Golang implementation of Hierarchical Timing Wheels. 3 | 4 | 此仓库的代码仅供学习使用,仓库对应的博客文章地址:https://www.luozhiyun.com/archives/444 -------------------------------------------------------------------------------- /bucket_test.go: -------------------------------------------------------------------------------- 1 | package timingwheel 2 | 3 | import "testing" 4 | 5 | func TestBucket_Flush(t *testing.T) { 6 | b := newBucket() 7 | 8 | b.Add(&Timer{}) 9 | b.Add(&Timer{}) 10 | l1 := b.timers.Len() 11 | if l1 != 2 { 12 | t.Fatalf("Got (%+v) != Want (%+v)", l1, 2) 13 | } 14 | 15 | b.Flush(func(*Timer) {}) 16 | l2 := b.timers.Len() 17 | if l2 != 0 { 18 | t.Fatalf("Got (%+v) != Want (%+v)", l2, 0) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example_scheduler_test.go: -------------------------------------------------------------------------------- 1 | package timingwheel_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/RussellLuo/timingwheel" 8 | ) 9 | 10 | type EveryScheduler struct { 11 | Interval time.Duration 12 | } 13 | 14 | func (s *EveryScheduler) Next(prev time.Time) time.Time { 15 | return prev.Add(s.Interval) 16 | } 17 | 18 | func Example_scheduleTimer() { 19 | tw := timingwheel.NewTimingWheel(time.Millisecond, 20) 20 | tw.Start() 21 | defer tw.Stop() 22 | 23 | exitC := make(chan time.Time) 24 | t := tw.ScheduleFunc(&EveryScheduler{time.Second}, func() { 25 | fmt.Println("The timer fires") 26 | exitC <- time.Now().UTC() 27 | }) 28 | 29 | <-exitC 30 | <-exitC 31 | 32 | // We need to stop the timer since it will be restarted again and again. 33 | for !t.Stop() { 34 | } 35 | 36 | // Output: 37 | // The timer fires 38 | // The timer fires 39 | } 40 | -------------------------------------------------------------------------------- /timingwheel_examples_test.go: -------------------------------------------------------------------------------- 1 | package timingwheel_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/RussellLuo/timingwheel" 8 | ) 9 | 10 | func Example_startTimer() { 11 | tw := timingwheel.NewTimingWheel(time.Second, 20) 12 | tw.Start() 13 | defer tw.Stop() 14 | 15 | exitC := make(chan time.Time, 2) 16 | tw.AfterFunc(time.Second, func() { 17 | fmt.Println("The timer fires") 18 | exitC <- time.Now().UTC() 19 | }) 20 | 21 | <-exitC 22 | 23 | // Output: 24 | // The timer fires 25 | } 26 | 27 | func Example_stopTimer() { 28 | tw := timingwheel.NewTimingWheel(time.Millisecond, 20) 29 | tw.Start() 30 | defer tw.Stop() 31 | 32 | t := tw.AfterFunc(time.Second, func() { 33 | fmt.Println("The timer fires") 34 | }) 35 | 36 | <-time.After(900 * time.Millisecond) 37 | // Stop the timer before it fires 38 | t.Stop() 39 | 40 | // Output: 41 | // 42 | } 43 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package timingwheel 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // truncate returns the result of rounding x toward zero to a multiple of m. 9 | // If m <= 0, Truncate returns x unchanged. 10 | func truncate(x, m int64) int64 { 11 | if m <= 0 { 12 | return x 13 | } 14 | return x - x%m 15 | } 16 | 17 | // timeToMs returns an integer number, which represents t in milliseconds. 18 | func timeToMs(t time.Time) int64 { 19 | return t.UnixNano() / int64(time.Millisecond) 20 | } 21 | 22 | // msToTime returns the UTC time corresponding to the given Unix time, 23 | // t milliseconds since January 1, 1970 UTC. 24 | func msToTime(t int64) time.Time { 25 | return time.Unix(0, t*int64(time.Millisecond)).UTC() 26 | } 27 | 28 | type waitGroupWrapper struct { 29 | sync.WaitGroup 30 | } 31 | 32 | func (w *waitGroupWrapper) Wrap(cb func()) { 33 | w.Add(1) 34 | go func() { 35 | cb() 36 | w.Done() 37 | }() 38 | } 39 | -------------------------------------------------------------------------------- /timingwheel_benchmark_test.go: -------------------------------------------------------------------------------- 1 | package timingwheel_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/RussellLuo/timingwheel" 8 | ) 9 | 10 | func genD(i int) time.Duration { 11 | return time.Duration(i%10000) * time.Millisecond 12 | } 13 | 14 | func BenchmarkTimingWheel_StartStop(b *testing.B) { 15 | tw := timingwheel.NewTimingWheel(time.Millisecond, 20) 16 | tw.Start() 17 | defer tw.Stop() 18 | 19 | cases := []struct { 20 | name string 21 | N int // the data size (i.e. number of existing timers) 22 | }{ 23 | {"N-1m", 1000000}, 24 | {"N-5m", 5000000}, 25 | {"N-10m", 10000000}, 26 | } 27 | for _, c := range cases { 28 | b.Run(c.name, func(b *testing.B) { 29 | base := make([]*timingwheel.Timer, c.N) 30 | for i := 0; i < len(base); i++ { 31 | base[i] = tw.AfterFunc(genD(i), func() {}) 32 | } 33 | b.ResetTimer() 34 | 35 | for i := 0; i < b.N; i++ { 36 | tw.AfterFunc(time.Second, func() {}).Stop() 37 | } 38 | 39 | b.StopTimer() 40 | for i := 0; i < len(base); i++ { 41 | base[i].Stop() 42 | } 43 | }) 44 | } 45 | } 46 | 47 | func BenchmarkStandardTimer_StartStop(b *testing.B) { 48 | cases := []struct { 49 | name string 50 | N int // the data size (i.e. number of existing timers) 51 | }{ 52 | {"N-1m", 1000000}, 53 | {"N-5m", 5000000}, 54 | {"N-10m", 10000000}, 55 | } 56 | for _, c := range cases { 57 | b.Run(c.name, func(b *testing.B) { 58 | base := make([]*time.Timer, c.N) 59 | for i := 0; i < len(base); i++ { 60 | base[i] = time.AfterFunc(genD(i), func() {}) 61 | } 62 | b.ResetTimer() 63 | 64 | for i := 0; i < b.N; i++ { 65 | time.AfterFunc(time.Second, func() {}).Stop() 66 | } 67 | 68 | b.StopTimer() 69 | for i := 0; i < len(base); i++ { 70 | base[i].Stop() 71 | } 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /timingwheel_test.go: -------------------------------------------------------------------------------- 1 | package timingwheel_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/RussellLuo/timingwheel" 8 | ) 9 | 10 | func TestTimingWheel_AfterFunc(t *testing.T) { 11 | tw := timingwheel.NewTimingWheel(time.Millisecond, 20) 12 | tw.Start() 13 | defer tw.Stop() 14 | 15 | durations := []time.Duration{ 16 | 1 * time.Millisecond, 17 | 5 * time.Millisecond, 18 | 10 * time.Millisecond, 19 | 50 * time.Millisecond, 20 | 100 * time.Millisecond, 21 | 500 * time.Millisecond, 22 | 1 * time.Second, 23 | } 24 | for _, d := range durations { 25 | t.Run("", func(t *testing.T) { 26 | exitC := make(chan time.Time) 27 | 28 | start := time.Now().UTC() 29 | tw.AfterFunc(d, func() { 30 | exitC <- time.Now().UTC() 31 | }) 32 | 33 | got := (<-exitC).Truncate(time.Millisecond) 34 | min := start.Add(d).Truncate(time.Millisecond) 35 | 36 | err := 5 * time.Millisecond 37 | if got.Before(min) || got.After(min.Add(err)) { 38 | t.Errorf("Timer(%s) expiration: want [%s, %s], got %s", d, min, min.Add(err), got) 39 | } 40 | }) 41 | } 42 | } 43 | 44 | type scheduler struct { 45 | intervals []time.Duration 46 | current int 47 | } 48 | 49 | func (s *scheduler) Next(prev time.Time) time.Time { 50 | if s.current >= len(s.intervals) { 51 | return time.Time{} 52 | } 53 | next := prev.Add(s.intervals[s.current]) 54 | s.current += 1 55 | return next 56 | } 57 | 58 | func TestTimingWheel_ScheduleFunc(t *testing.T) { 59 | tw := timingwheel.NewTimingWheel(time.Millisecond, 20) 60 | tw.Start() 61 | defer tw.Stop() 62 | 63 | s := &scheduler{intervals: []time.Duration{ 64 | 1 * time.Millisecond, // start + 1ms 65 | 4 * time.Millisecond, // start + 5ms 66 | 5 * time.Millisecond, // start + 10ms 67 | 40 * time.Millisecond, // start + 50ms 68 | 50 * time.Millisecond, // start + 100ms 69 | 400 * time.Millisecond, // start + 500ms 70 | 500 * time.Millisecond, // start + 1s 71 | }} 72 | 73 | exitC := make(chan time.Time, len(s.intervals)) 74 | 75 | start := time.Now().UTC() 76 | tw.ScheduleFunc(s, func() { 77 | exitC <- time.Now().UTC() 78 | }) 79 | 80 | accum := time.Duration(0) 81 | for _, d := range s.intervals { 82 | got := (<-exitC).Truncate(time.Millisecond) 83 | accum += d 84 | min := start.Add(accum).Truncate(time.Millisecond) 85 | 86 | err := 5 * time.Millisecond 87 | if got.Before(min) || got.After(min.Add(err)) { 88 | t.Errorf("Timer(%s) expiration: want [%s, %s], got %s", accum, min, min.Add(err), got) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /bucket.go: -------------------------------------------------------------------------------- 1 | package timingwheel 2 | 3 | import ( 4 | "container/list" 5 | "sync" 6 | "sync/atomic" 7 | "unsafe" 8 | ) 9 | 10 | // Timer represents a single event. When the Timer expires, the given 11 | // task will be executed. 12 | type Timer struct { 13 | expiration int64 // in milliseconds 14 | task func() 15 | 16 | // The bucket that holds the list to which this timer's element belongs. 17 | // 18 | // NOTE: This field may be updated and read concurrently, 19 | // through Timer.Stop() and Bucket.Flush(). 20 | b unsafe.Pointer // type: *bucket 21 | 22 | // The timer's element. 23 | element *list.Element 24 | } 25 | 26 | func (t *Timer) getBucket() *bucket { 27 | return (*bucket)(atomic.LoadPointer(&t.b)) 28 | } 29 | 30 | func (t *Timer) setBucket(b *bucket) { 31 | atomic.StorePointer(&t.b, unsafe.Pointer(b)) 32 | } 33 | 34 | // Stop prevents the Timer from firing. It returns true if the call 35 | // stops the timer, false if the timer has already expired or been stopped. 36 | // 37 | // If the timer t has already expired and the t.task has been started in its own 38 | // goroutine; Stop does not wait for t.task to complete before returning. If the caller 39 | // needs to know whether t.task is completed, it must coordinate with t.task explicitly. 40 | func (t *Timer) Stop() bool { 41 | stopped := false 42 | for b := t.getBucket(); b != nil; b = t.getBucket() { 43 | // If b.Remove is called just after the timing wheel's goroutine has: 44 | // 1. removed t from b (through b.Flush -> b.remove) 45 | // 2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add) 46 | // this may fail to remove t due to the change of t's bucket. 47 | stopped = b.Remove(t) 48 | 49 | // Thus, here we re-get t's possibly new bucket (nil for case 1, or ab (non-nil) for case 2), 50 | // and retry until the bucket becomes nil, which indicates that t has finally been removed. 51 | } 52 | return stopped 53 | } 54 | 55 | type bucket struct { 56 | // 64-bit atomic operations require 64-bit alignment, but 32-bit 57 | // compilers do not ensure it. So we must keep the 64-bit field 58 | // as the first field of the struct. 59 | // 60 | // For more explanations, see https://golang.org/pkg/sync/atomic/#pkg-note-BUG 61 | // and https://go101.org/article/memory-layout.html. 62 | // 任务的过期时间 63 | expiration int64 64 | 65 | mu sync.Mutex 66 | // 相同过期时间的任务队列 67 | timers *list.List 68 | } 69 | 70 | func newBucket() *bucket { 71 | return &bucket{ 72 | timers: list.New(), 73 | expiration: -1, 74 | } 75 | } 76 | 77 | func (b *bucket) Expiration() int64 { 78 | return atomic.LoadInt64(&b.expiration) 79 | } 80 | 81 | func (b *bucket) SetExpiration(expiration int64) bool { 82 | return atomic.SwapInt64(&b.expiration, expiration) != expiration 83 | } 84 | 85 | func (b *bucket) Add(t *Timer) { 86 | b.mu.Lock() 87 | 88 | e := b.timers.PushBack(t) 89 | t.setBucket(b) 90 | t.element = e 91 | 92 | b.mu.Unlock() 93 | } 94 | 95 | func (b *bucket) remove(t *Timer) bool { 96 | if t.getBucket() != b { 97 | // If remove is called from t.Stop, and this happens just after the timing wheel's goroutine has: 98 | // 1. removed t from b (through b.Flush -> b.remove) 99 | // 2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add) 100 | // then t.getBucket will return nil for case 1, or ab (non-nil) for case 2. 101 | // In either case, the returned value does not equal to b. 102 | return false 103 | } 104 | b.timers.Remove(t.element) 105 | t.setBucket(nil) 106 | t.element = nil 107 | return true 108 | } 109 | 110 | func (b *bucket) Remove(t *Timer) bool { 111 | b.mu.Lock() 112 | defer b.mu.Unlock() 113 | return b.remove(t) 114 | } 115 | 116 | func (b *bucket) Flush(reinsert func(*Timer)) { 117 | var ts []*Timer 118 | 119 | b.mu.Lock() 120 | // 循环获取bucket队列节点 121 | for e := b.timers.Front(); e != nil; { 122 | next := e.Next() 123 | 124 | t := e.Value.(*Timer) 125 | // 将头节点移除bucket队列 126 | b.remove(t) 127 | ts = append(ts, t) 128 | 129 | e = next 130 | } 131 | b.mu.Unlock() 132 | 133 | b.SetExpiration(-1) // TODO: Improve the coordination with b.Add() 134 | 135 | for _, t := range ts { 136 | reinsert(t) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /delayqueue/delayqueue.go: -------------------------------------------------------------------------------- 1 | package delayqueue 2 | 3 | import ( 4 | "container/heap" 5 | "sync" 6 | "sync/atomic" 7 | "time" 8 | ) 9 | 10 | // The start of PriorityQueue implementation. 11 | // Borrowed from https://github.com/nsqio/nsq/blob/master/internal/pqueue/pqueue.go 12 | 13 | type item struct { 14 | Value interface{} 15 | Priority int64 16 | Index int 17 | } 18 | 19 | // this is a priority queue as implemented by a min heap 20 | // ie. the 0th element is the *lowest* value 21 | type priorityQueue []*item 22 | 23 | func newPriorityQueue(capacity int) priorityQueue { 24 | return make(priorityQueue, 0, capacity) 25 | } 26 | 27 | func (pq priorityQueue) Len() int { 28 | return len(pq) 29 | } 30 | 31 | func (pq priorityQueue) Less(i, j int) bool { 32 | return pq[i].Priority < pq[j].Priority 33 | } 34 | 35 | func (pq priorityQueue) Swap(i, j int) { 36 | pq[i], pq[j] = pq[j], pq[i] 37 | pq[i].Index = i 38 | pq[j].Index = j 39 | } 40 | 41 | func (pq *priorityQueue) Push(x interface{}) { 42 | n := len(*pq) 43 | c := cap(*pq) 44 | if n+1 > c { 45 | npq := make(priorityQueue, n, c*2) 46 | copy(npq, *pq) 47 | *pq = npq 48 | } 49 | *pq = (*pq)[0 : n+1] 50 | item := x.(*item) 51 | item.Index = n 52 | (*pq)[n] = item 53 | } 54 | 55 | func (pq *priorityQueue) Pop() interface{} { 56 | n := len(*pq) 57 | c := cap(*pq) 58 | if n < (c/2) && c > 25 { 59 | npq := make(priorityQueue, n, c/2) 60 | copy(npq, *pq) 61 | *pq = npq 62 | } 63 | item := (*pq)[n-1] 64 | item.Index = -1 65 | *pq = (*pq)[0 : n-1] 66 | return item 67 | } 68 | 69 | func (pq *priorityQueue) PeekAndShift(max int64) (*item, int64) { 70 | if pq.Len() == 0 { 71 | return nil, 0 72 | } 73 | 74 | item := (*pq)[0] 75 | if item.Priority > max { 76 | return nil, item.Priority - max 77 | } 78 | heap.Remove(pq, 0) 79 | 80 | return item, 0 81 | } 82 | 83 | // The end of PriorityQueue implementation. 84 | 85 | // DelayQueue is an unbounded blocking queue of *Delayed* elements, in which 86 | // an element can only be taken when its delay has expired. The head of the 87 | // queue is the *Delayed* element whose delay expired furthest in the past. 88 | type DelayQueue struct { 89 | C chan interface{} 90 | 91 | mu sync.Mutex 92 | pq priorityQueue 93 | 94 | // Similar to the sleeping state of runtime.timers. 95 | sleeping int32 96 | wakeupC chan struct{} 97 | } 98 | 99 | // New creates an instance of delayQueue with the specified size. 100 | func New(size int) *DelayQueue { 101 | return &DelayQueue{ 102 | C: make(chan interface{}), 103 | pq: newPriorityQueue(size), 104 | wakeupC: make(chan struct{}), 105 | } 106 | } 107 | 108 | // Offer inserts the element into the current queue. 109 | func (dq *DelayQueue) Offer(elem interface{}, expiration int64) { 110 | item := &item{Value: elem, Priority: expiration} 111 | 112 | dq.mu.Lock() 113 | heap.Push(&dq.pq, item) 114 | index := item.Index 115 | dq.mu.Unlock() 116 | 117 | if index == 0 { 118 | // A new item with the earliest expiration is added. 119 | if atomic.CompareAndSwapInt32(&dq.sleeping, 1, 0) { 120 | dq.wakeupC <- struct{}{} 121 | } 122 | } 123 | } 124 | 125 | // Poll starts an infinite loop, in which it continually waits for an element 126 | // to expire and then send the expired element to the channel C. 127 | func (dq *DelayQueue) Poll(exitC chan struct{}, nowF func() int64) { 128 | for { 129 | now := nowF() 130 | 131 | dq.mu.Lock() 132 | item, delta := dq.pq.PeekAndShift(now) 133 | if item == nil { 134 | // No items left or at least one item is pending. 135 | 136 | // We must ensure the atomicity of the whole operation, which is 137 | // composed of the above PeekAndShift and the following StoreInt32, 138 | // to avoid possible race conditions between Offer and Poll. 139 | atomic.StoreInt32(&dq.sleeping, 1) 140 | } 141 | dq.mu.Unlock() 142 | 143 | if item == nil { 144 | if delta == 0 { 145 | // No items left. 146 | select { 147 | case <-dq.wakeupC: 148 | // Wait until a new item is added. 149 | continue 150 | case <-exitC: 151 | goto exit 152 | } 153 | } else if delta > 0 { 154 | // At least one item is pending. 155 | select { 156 | case <-dq.wakeupC: 157 | // A new item with an "earlier" expiration than the current "earliest" one is added. 158 | continue 159 | case <-time.After(time.Duration(delta) * time.Millisecond): 160 | // The current "earliest" item expires. 161 | 162 | // Reset the sleeping state since there's no need to receive from wakeupC. 163 | if atomic.SwapInt32(&dq.sleeping, 0) == 0 { 164 | // A caller of Offer() is being blocked on sending to wakeupC, 165 | // drain wakeupC to unblock the caller. 166 | <-dq.wakeupC 167 | } 168 | continue 169 | case <-exitC: 170 | goto exit 171 | } 172 | } 173 | } 174 | 175 | select { 176 | case dq.C <- item.Value: 177 | // The expired element has been sent out successfully. 178 | case <-exitC: 179 | goto exit 180 | } 181 | } 182 | 183 | exit: 184 | // Reset the states 185 | atomic.StoreInt32(&dq.sleeping, 0) 186 | } 187 | -------------------------------------------------------------------------------- /timingwheel.go: -------------------------------------------------------------------------------- 1 | package timingwheel 2 | 3 | import ( 4 | "errors" 5 | "sync/atomic" 6 | "time" 7 | "unsafe" 8 | 9 | "github.com/RussellLuo/timingwheel/delayqueue" 10 | ) 11 | 12 | // TimingWheel is an implementation of Hierarchical Timing Wheels. 13 | type TimingWheel struct { 14 | // 时间跨度,单位是毫秒 15 | tick int64 // in milliseconds 16 | // 时间轮个数 17 | wheelSize int64 18 | // 总跨度 19 | interval int64 // in milliseconds 20 | // 当前指针指向时间 21 | currentTime int64 // in milliseconds 22 | // 时间格列表 23 | buckets []*bucket 24 | // 延迟队列 25 | queue *delayqueue.DelayQueue 26 | 27 | // The higher-level overflow wheel. 28 | // 29 | // NOTE: This field may be updated and read concurrently, through Add(). 30 | // 上级的时间轮饮用 31 | overflowWheel unsafe.Pointer // type: *TimingWheel 32 | 33 | exitC chan struct{} 34 | waitGroup waitGroupWrapper 35 | } 36 | 37 | // NewTimingWheel creates an instance of TimingWheel with the given tick and wheelSize. 38 | func NewTimingWheel(tick time.Duration, wheelSize int64) *TimingWheel { 39 | tickMs := int64(tick / time.Millisecond) 40 | if tickMs <= 0 { 41 | panic(errors.New("tick must be greater than or equal to 1ms")) 42 | } 43 | 44 | startMs := timeToMs(time.Now().UTC()) 45 | 46 | return newTimingWheel( 47 | tickMs, 48 | wheelSize, 49 | startMs, 50 | delayqueue.New(int(wheelSize)), 51 | ) 52 | } 53 | 54 | // newTimingWheel is an internal helper function that really creates an instance of TimingWheel. 55 | func newTimingWheel(tickMs int64, wheelSize int64, startMs int64, queue *delayqueue.DelayQueue) *TimingWheel { 56 | buckets := make([]*bucket, wheelSize) 57 | for i := range buckets { 58 | buckets[i] = newBucket() 59 | } 60 | return &TimingWheel{ 61 | tick: tickMs, 62 | wheelSize: wheelSize, 63 | currentTime: truncate(startMs, tickMs), 64 | interval: tickMs * wheelSize, 65 | buckets: buckets, 66 | queue: queue, 67 | exitC: make(chan struct{}), 68 | } 69 | } 70 | 71 | // add inserts the timer t into the current timing wheel. 72 | func (tw *TimingWheel) add(t *Timer) bool { 73 | currentTime := atomic.LoadInt64(&tw.currentTime) 74 | // 已经过期 75 | if t.expiration < currentTime+tw.tick { 76 | // Already expired 77 | return false 78 | // 到期时间在第一层环内 79 | } else if t.expiration < currentTime+tw.interval { 80 | // Put it into its own bucket 81 | // 获取时间轮的位置 82 | virtualID := t.expiration / tw.tick 83 | b := tw.buckets[virtualID%tw.wheelSize] 84 | // 将任务放入到bucket队列中 85 | b.Add(t) 86 | 87 | // Set the bucket expiration time 88 | // 如果是相同的时间,那么返回false,防止被多次插入到队列中 89 | if b.SetExpiration(virtualID * tw.tick) { 90 | // The bucket needs to be enqueued since it was an expired bucket. 91 | // We only need to enqueue the bucket when its expiration time has changed, 92 | // i.e. the wheel has advanced and this bucket get reused with a new expiration. 93 | // Any further calls to set the expiration within the same wheel cycle will 94 | // pass in the same value and hence return false, thus the bucket with the 95 | // same expiration will not be enqueued multiple times. 96 | // 将该bucket加入到延迟队列中 97 | tw.queue.Offer(b, b.Expiration()) 98 | } 99 | 100 | return true 101 | } else { 102 | // Out of the interval. Put it into the overflow wheel 103 | // 如果放入的到期时间超过第一层时间轮,那么放到上一层中去 104 | overflowWheel := atomic.LoadPointer(&tw.overflowWheel) 105 | if overflowWheel == nil { 106 | atomic.CompareAndSwapPointer( 107 | &tw.overflowWheel, 108 | nil, 109 | // 需要注意的是,这里tick变成了interval 110 | unsafe.Pointer(newTimingWheel( 111 | tw.interval, 112 | tw.wheelSize, 113 | currentTime, 114 | tw.queue, 115 | )), 116 | ) 117 | overflowWheel = atomic.LoadPointer(&tw.overflowWheel) 118 | } 119 | // 往上递归 120 | return (*TimingWheel)(overflowWheel).add(t) 121 | } 122 | } 123 | 124 | // addOrRun inserts the timer t into the current timing wheel, or run the 125 | // timer's task if it has already expired. 126 | func (tw *TimingWheel) addOrRun(t *Timer) { 127 | // 如果已经过期,那么直接执行 128 | if !tw.add(t) { 129 | // Already expired 130 | 131 | // Like the standard time.AfterFunc (https://golang.org/pkg/time/#AfterFunc), 132 | // always execute the timer's task in its own goroutine. 133 | // 异步执行定时任务 134 | go t.task() 135 | } 136 | } 137 | 138 | func (tw *TimingWheel) advanceClock(expiration int64) { 139 | currentTime := atomic.LoadInt64(&tw.currentTime) 140 | // 过期时间大于等于(当前时间+tick) 141 | if expiration >= currentTime+tw.tick { 142 | // 将currentTime设置为expiration,从而推进currentTime 143 | currentTime = truncate(expiration, tw.tick) 144 | atomic.StoreInt64(&tw.currentTime, currentTime) 145 | 146 | // Try to advance the clock of the overflow wheel if present 147 | // 如果有上层时间轮,那么递归调用上层时间轮的引用 148 | overflowWheel := atomic.LoadPointer(&tw.overflowWheel) 149 | if overflowWheel != nil { 150 | (*TimingWheel)(overflowWheel).advanceClock(currentTime) 151 | } 152 | } 153 | } 154 | 155 | // Start starts the current timing wheel. 156 | func (tw *TimingWheel) Start() { 157 | // Poll会执行一个无限循环,将到期的元素放入到queue的C管道中 158 | tw.waitGroup.Wrap(func() { 159 | tw.queue.Poll(tw.exitC, func() int64 { 160 | return timeToMs(time.Now().UTC()) 161 | }) 162 | }) 163 | // 开启无限循环获取queue中C的数据 164 | tw.waitGroup.Wrap(func() { 165 | for { 166 | select { 167 | // 从队列里面出来的数据都是到期的bucket 168 | case elem := <-tw.queue.C: 169 | b := elem.(*bucket) 170 | // 时间轮会将当前时间 currentTime 往前移动到 bucket的到期时间 171 | tw.advanceClock(b.Expiration()) 172 | // 取出bucket队列的数据,并调用addOrRun方法执行 173 | b.Flush(tw.addOrRun) 174 | case <-tw.exitC: 175 | return 176 | } 177 | } 178 | }) 179 | } 180 | 181 | // Stop stops the current timing wheel. 182 | // 183 | // If there is any timer's task being running in its own goroutine, Stop does 184 | // not wait for the task to complete before returning. If the caller needs to 185 | // know whether the task is completed, it must coordinate with the task explicitly. 186 | func (tw *TimingWheel) Stop() { 187 | close(tw.exitC) 188 | tw.waitGroup.Wait() 189 | } 190 | 191 | // AfterFunc waits for the duration to elapse and then calls f in its own goroutine. 192 | // It returns a Timer that can be used to cancel the call using its Stop method. 193 | func (tw *TimingWheel) AfterFunc(d time.Duration, f func()) *Timer { 194 | t := &Timer{ 195 | expiration: timeToMs(time.Now().UTC().Add(d)), 196 | task: f, 197 | } 198 | tw.addOrRun(t) 199 | return t 200 | } 201 | 202 | // Scheduler determines the execution plan of a task. 203 | type Scheduler interface { 204 | // Next returns the next execution time after the given (previous) time. 205 | // It will return a zero time if no next time is scheduled. 206 | // 207 | // All times must be UTC. 208 | Next(time.Time) time.Time 209 | } 210 | 211 | // ScheduleFunc calls f (in its own goroutine) according to the execution 212 | // plan scheduled by s. It returns a Timer that can be used to cancel the 213 | // call using its Stop method. 214 | // 215 | // If the caller want to terminate the execution plan halfway, it must 216 | // stop the timer and ensure that the timer is stopped actually, since in 217 | // the current implementation, there is a gap between the expiring and the 218 | // restarting of the timer. The wait time for ensuring is short since the 219 | // gap is very small. 220 | // 221 | // Internally, ScheduleFunc will ask the first execution time (by calling 222 | // s.Next()) initially, and create a timer if the execution time is non-zero. 223 | // Afterwards, it will ask the next execution time each time f is about to 224 | // be executed, and f will be called at the next execution time if the time 225 | // is non-zero. 226 | func (tw *TimingWheel) ScheduleFunc(s Scheduler, f func()) (t *Timer) { 227 | expiration := s.Next(time.Now().UTC()) 228 | if expiration.IsZero() { 229 | // No time is scheduled, return nil. 230 | return 231 | } 232 | 233 | t = &Timer{ 234 | expiration: timeToMs(expiration), 235 | task: func() { 236 | // Schedule the task to execute at the next time if possible. 237 | expiration := s.Next(msToTime(t.expiration)) 238 | if !expiration.IsZero() { 239 | t.expiration = timeToMs(expiration) 240 | tw.addOrRun(t) 241 | } 242 | 243 | // Actually execute the task. 244 | f() 245 | }, 246 | } 247 | tw.addOrRun(t) 248 | 249 | return 250 | } 251 | --------------------------------------------------------------------------------