├── .gitignore ├── go.mod ├── example ├── simple.go ├── delete.go └── complex.go ├── gtimer.go ├── common.go ├── .github └── workflows │ └── golang-ci.yml ├── priority_queue_test.go ├── LICENSE ├── atomic_bool.go ├── .golangci.yml ├── priority_queue.go ├── README_zh.md ├── README.md └── super_timer.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vearne/gtimer 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /example/simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/vearne/gtimer" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | startTime := time.Now() 11 | gtimer.Add(3*time.Second, func() { 12 | fmt.Println(time.Since(startTime)) 13 | }) 14 | go func() { 15 | time.Sleep(5 * time.Second) 16 | gtimer.Stop() 17 | }() 18 | gtimer.Wait() 19 | fmt.Println("---end---") 20 | } 21 | -------------------------------------------------------------------------------- /example/delete.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/vearne/gtimer" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | startTime := time.Now() 11 | gtimer.Add(3*time.Second, func() { 12 | fmt.Println("task1", time.Since(startTime)) 13 | }) 14 | task2 := gtimer.Add(5*time.Second, func() { 15 | fmt.Println("task2", time.Since(startTime)) 16 | }) 17 | gtimer.Add(7*time.Second, func() { 18 | fmt.Println("task3", time.Since(startTime)) 19 | }) 20 | fmt.Println("before remove, size:", gtimer.Size()) 21 | go func() { 22 | time.Sleep(10 * time.Second) 23 | gtimer.Stop() 24 | }() 25 | 26 | gtimer.Remove(task2) 27 | fmt.Println("after remove, size:", gtimer.Size()) 28 | gtimer.Wait() 29 | fmt.Println("---end---") 30 | } 31 | -------------------------------------------------------------------------------- /example/complex.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/vearne/gtimer" 6 | "time" 7 | ) 8 | 9 | func doSomeThing(scheduledExecTime time.Time, param interface{}) { 10 | fmt.Println("Scheduled Execution Time:", scheduledExecTime, "Real Execution Time:", time.Now()) 11 | fmt.Println("hello ", param) 12 | } 13 | 14 | func main() { 15 | startTime := time.Now() 16 | // change default timer 17 | //gtimer.Set(gtimer.NewSuperTimer(20)) 18 | gtimer.AddComplex(startTime.Add(3*time.Second), doSomeThing, "world") 19 | gtimer.AddComplex(startTime.Add(4*time.Second), doSomeThing, "sky") 20 | gtimer.AddComplex(startTime.Add(2*time.Second), doSomeThing, "groud") 21 | go func() { 22 | time.Sleep(5 * time.Second) 23 | gtimer.Stop() 24 | }() 25 | gtimer.Wait() 26 | fmt.Println("---end---") 27 | } 28 | -------------------------------------------------------------------------------- /gtimer.go: -------------------------------------------------------------------------------- 1 | package gtimer 2 | 3 | import "time" 4 | 5 | var ( 6 | defaultTimer *SuperTimer 7 | ) 8 | 9 | func init() { 10 | defaultTimer = NewSuperTimer(10) 11 | } 12 | 13 | func Set(timer *SuperTimer) { 14 | defaultTimer.Stop() 15 | defaultTimer = timer 16 | } 17 | 18 | func Add(d time.Duration, f func()) *Item { 19 | task := NewDelayedItemFunc(time.Now().Add(d), nil, func(t time.Time, i interface{}) { 20 | f() 21 | }) 22 | defaultTimer.Add(task) 23 | return task 24 | } 25 | 26 | func AddComplex(scheduledExecTime time.Time, f func(scheduledExecTime time.Time, param interface{}), 27 | param interface{}) *Item { 28 | task := NewDelayedItemFunc(scheduledExecTime, param, f) 29 | defaultTimer.Add(task) 30 | return task 31 | } 32 | 33 | func Remove(task *Item) { 34 | defaultTimer.Remove(task) 35 | } 36 | 37 | func Stop() { 38 | defaultTimer.Stop() 39 | } 40 | 41 | func Wait() { 42 | defaultTimer.Wait() 43 | } 44 | 45 | func Size() int { 46 | return defaultTimer.Size() 47 | } 48 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package gtimer 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Delayed interface { 8 | GetDelay() time.Duration 9 | } 10 | 11 | //nolint: govet 12 | // An Item is something we manage in a priority queue. 13 | type Item struct { 14 | // when task is ready, execute OnTrigger function 15 | OnTrigger func(scheduledExecTime time.Time, param interface{}) 16 | priority int64 // The priority of the item in the queue. 17 | // The index is needed by update and is maintained by the heap.Interface methods. 18 | index int // The index of the item in the heap. 19 | value interface{} // The value of the item; arbitrary. 20 | } 21 | 22 | // triggerTime is time of the task should be execute 23 | func NewDelayedItemFunc(triggerTime time.Time, param interface{}, f func(time.Time, interface{})) *Item { 24 | item := Item{} 25 | item.priority = triggerTime.UnixNano() 26 | item.value = param 27 | item.OnTrigger = f 28 | return &item 29 | } 30 | 31 | func (item *Item) GetDelay() time.Duration { 32 | return time.Duration(item.priority - time.Now().UnixNano()) 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/golang-ci.yml: -------------------------------------------------------------------------------- 1 | name: golang-ci 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the main branch 6 | push: 7 | branches: 8 | - main 9 | - master 10 | pull_request: 11 | branches: 12 | - main 13 | - master 14 | # Allows you to run this workflow manually from the Actions tab 15 | workflow_dispatch: 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: checkout 22 | uses: actions/checkout@v2 23 | - name: Set up Go 24 | uses: actions/setup-go@v2 25 | with: 26 | go-version: 1.19 27 | - name: Test 28 | run: go test -v ./ 29 | 30 | lint: 31 | runs-on: ubuntu-latest 32 | container: 33 | image: golangci/golangci-lint:v1.40 34 | steps: 35 | - name: checkout 36 | uses: actions/checkout@v2 37 | - name: Set up Go 38 | uses: actions/setup-go@v2 39 | with: 40 | go-version: 1.19 41 | - name: golangci-lint 42 | run: golangci-lint run --modules-download-mode=vendor 43 | -------------------------------------------------------------------------------- /priority_queue_test.go: -------------------------------------------------------------------------------- 1 | package gtimer 2 | 3 | import ( 4 | "container/heap" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func Test_Priority_queue(t *testing.T) { 10 | 11 | // Some items and their priorities. 12 | items := map[string]int64{ 13 | "banana": 13, "apple": 2, "pear": 4, 14 | } 15 | 16 | // Create a priority queue, put the items in it, and 17 | // establish the priority queue (heap) invariants. 18 | pq := make(PriorityQueue, len(items)) 19 | i := 0 20 | for value, priority := range items { 21 | pq[i] = &Item{ 22 | value: value, 23 | priority: priority, 24 | index: i, 25 | } 26 | i++ 27 | } 28 | heap.Init(&pq) 29 | // Insert a new item and then modify its priority. 30 | item := &Item{ 31 | value: "orange", 32 | priority: 1, 33 | } 34 | heap.Push(&pq, item) 35 | item = &Item{ 36 | value: "orange2", 37 | priority: 5, 38 | } 39 | heap.Push(&pq, item) 40 | 41 | // Take the items out; they arrive in decreasing priority order. 42 | for pq.Len() > 0 { 43 | item := heap.Pop(&pq).(*Item) 44 | fmt.Printf("%.2d:%s \n", item.priority, item.value) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 vearne 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 | -------------------------------------------------------------------------------- /atomic_bool.go: -------------------------------------------------------------------------------- 1 | package gtimer 2 | 3 | import "sync/atomic" 4 | 5 | // noCopy may be embedded into structs which must not be copied 6 | // after the first use. 7 | // 8 | // See https://github.com/golang/go/issues/8005#issuecomment-190753527 9 | // for details. 10 | type noCopy struct{} 11 | 12 | // Lock is a no-op used by -copylocks checker from `go vet`. 13 | func (*noCopy) Lock() {} 14 | 15 | // atomicBool is a wrapper around uint32 for usage as a boolean value with 16 | // atomic access. 17 | type AtomicBool struct { 18 | _noCopy noCopy //nolint:unused 19 | value uint32 20 | } 21 | 22 | func NewAtomicBool(flag bool) *AtomicBool { 23 | ab := &AtomicBool{} 24 | ab.Set(flag) 25 | return ab 26 | } 27 | 28 | // IsSet returns whether the current boolean value is true 29 | func (ab *AtomicBool) IsSet() bool { 30 | return atomic.LoadUint32(&ab.value) > 0 31 | } 32 | 33 | // Set sets the value of the bool regardless of the previous value 34 | func (ab *AtomicBool) Set(value bool) { 35 | if value { 36 | atomic.StoreUint32(&ab.value, 1) 37 | } else { 38 | atomic.StoreUint32(&ab.value, 0) 39 | } 40 | } 41 | 42 | func (ab *AtomicBool) IsTrue() bool { 43 | return ab.IsSet() 44 | } 45 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true # 关闭其他linter 3 | enable: # 下面是开启的linter列表,之后的英文注释介绍了相应linter的功能 4 | - deadcode # Finds unused code 5 | - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases 6 | - gosimple # Linter for Go source code that specializes in simplifying a code 7 | - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string 8 | - ineffassign # Detects when assignments to existing variables are not used 9 | - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks 10 | - structcheck # Finds unused struct fields 11 | - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code 12 | - unused # Checks Go code for unused constants, variables, functions and types 13 | - varcheck # Finds unused global variables and constants 14 | - scopelint # Scopelint checks for unpinned variables in go programs 15 | # - golint # Carry out the stylistic conventions put forth in Effective Go and CodeReviewComments 16 | 17 | linters-settings: 18 | govet: # 对于linter govet,我们手动开启了它的某些扫描规则 19 | check-shadowing: true 20 | enable-all: true 21 | 22 | run: 23 | skip-dirs: 24 | - example 25 | 26 | -------------------------------------------------------------------------------- /priority_queue.go: -------------------------------------------------------------------------------- 1 | // This example demonstrates a priority queue built using the heap interface. 2 | package gtimer 3 | 4 | import ( 5 | "container/heap" 6 | ) 7 | 8 | // A PriorityQueue implements heap.Interface and holds Items. 9 | type PriorityQueue []*Item 10 | 11 | func (pq *PriorityQueue) Len() int { return len(*pq) } 12 | 13 | func (pq *PriorityQueue) Less(i, j int) bool { 14 | return (*pq)[i].priority < (*pq)[j].priority 15 | } 16 | 17 | func (pq *PriorityQueue) Swap(i, j int) { 18 | (*pq)[i], (*pq)[j] = (*pq)[j], (*pq)[i] 19 | (*pq)[i].index = i 20 | (*pq)[j].index = j 21 | } 22 | 23 | func (pq *PriorityQueue) Push(x interface{}) { 24 | n := len(*pq) 25 | item := x.(*Item) 26 | item.index = n 27 | *pq = append(*pq, item) 28 | } 29 | 30 | func (pq *PriorityQueue) Pop() interface{} { 31 | old := *pq 32 | n := len(old) 33 | item := old[n-1] 34 | item.index = -1 // for safety 35 | *pq = old[0 : n-1] 36 | return item 37 | } 38 | 39 | // update modifies the priority and value of an Item in the queue. 40 | func (pq *PriorityQueue) Update(item *Item, value string, priority int64) { 41 | item.value = value 42 | item.priority = priority 43 | heap.Fix(pq, item.index) 44 | } 45 | 46 | func (pq *PriorityQueue) Remove(item *Item) { 47 | heap.Remove(pq, item.index) 48 | } 49 | 50 | func (pq *PriorityQueue) Peek() interface{} { 51 | old := *pq 52 | n := len(old) 53 | if n <= 0 { 54 | return nil 55 | } 56 | return old[0] 57 | } 58 | 59 | func (pq *PriorityQueue) Clear() { 60 | *pq = make([]*Item, 0) 61 | } 62 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # timer 2 | [![golang-ci](https://github.com/vearne/gtimer/actions/workflows/golang-ci.yml/badge.svg)](https://github.com/vearne/gtimer/actions/workflows/golang-ci.yml) 3 | 4 | 基于delayqueue实现的定时器 5 | 6 | * [English README](https://github.com/vearne/gtimer/blob/master/README.md) 7 | 8 | # 实现 9 | 实现受到了Java DelayQueue.java的启发 10 | 源码地址 11 | [DelayQueue.java](http://www.docjar.com/html/api/java/util/concurrent/DelayQueue.java.html) 12 | 13 | 依赖的几个结构依次为为 14 | timer -> delayqueue -> priorityqueue -> heap 15 | 16 | # 安装 17 | ## Install: 18 | 19 | ``` 20 | go get -u github.com/vearne/gtimer 21 | ``` 22 | ## Import: 23 | ``` 24 | import "github.com/vearne/gtimer" 25 | ``` 26 | 27 | 28 | ## 快速开始 29 | [更多示例](https://github.com/vearne/gtimer/blob/master/example) 30 | ``` 31 | package main 32 | 33 | import ( 34 | "fmt" 35 | "github.com/vearne/gtimer" 36 | "time" 37 | ) 38 | 39 | func main() { 40 | startTime := time.Now() 41 | gtimer.Add(3*time.Second, func() { 42 | fmt.Println(time.Since(startTime)) 43 | }) 44 | go func() { 45 | time.Sleep(5 * time.Second) 46 | gtimer.Stop() 47 | }() 48 | gtimer.Wait() 49 | fmt.Println("---end---") 50 | } 51 | ``` 52 | 53 | ## 性能 54 | 55 | `CPU Model Name`: 2.3 GHz Intel Core i5 56 | `CPU Processors`: 4 57 | `Memory`: 8GB 58 | 59 | ### 压测结果 60 | 61 | 62 | | 生产者数量 | 消费者数量 | QPS | 63 | | ---------:| ----------:| --------:| 64 | | 1| 1 | 285714 | 65 | | 10| 10 | 90090 | 66 | | 10| 100 | 89285 | 67 | | 100| 100 | 23255 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # timer 2 | [![golang-ci](https://github.com/vearne/gtimer/actions/workflows/golang-ci.yml/badge.svg)](https://github.com/vearne/gtimer/actions/workflows/golang-ci.yml) 3 | 4 | Timer based on delayqueue 5 | 6 | * [中文 README](https://github.com/vearne/gtimer/blob/master/README_zh.md) 7 | 8 | # Design and implementation 9 | The implementation is inspired by Java DelayQueue.java 10 | Portal: 11 | [DelayQueue.java](http://www.docjar.com/html/api/java/util/concurrent/DelayQueue.java.html) 12 | 13 | The dependent structures are as follows: 14 | 15 | timer -> delayqueue -> priorityqueue -> heap 16 | 17 | # Installation 18 | ## Install: 19 | 20 | ``` 21 | go get -u github.com/vearne/gtimer 22 | ``` 23 | ## Import: 24 | ``` 25 | import "github.com/vearne/gtimer" 26 | ``` 27 | 28 | 29 | ## Quick Start 30 | [more examples](https://github.com/vearne/gtimer/blob/master/example) 31 | ``` 32 | package main 33 | 34 | import ( 35 | "fmt" 36 | "github.com/vearne/gtimer" 37 | "time" 38 | ) 39 | 40 | func main() { 41 | startTime := time.Now() 42 | gtimer.Add(3*time.Second, func() { 43 | fmt.Println(time.Since(startTime)) 44 | }) 45 | go func() { 46 | time.Sleep(5 * time.Second) 47 | gtimer.Stop() 48 | }() 49 | gtimer.Wait() 50 | fmt.Println("---end---") 51 | } 52 | ``` 53 | 54 | ## Performance 55 | 56 | `CPU Model Name`: 2.3 GHz Intel Core i5 57 | `CPU Processors`: 4 58 | `Memory`: 8GB 59 | 60 | ### Benchmark Test Results 61 | 62 | 63 | | produce goroutines count | consume goroutines count | qps(per second) | 64 | | ---------:| ----------:| --------:| 65 | | 1| 1 | 285714 | 66 | | 10| 10 | 90090 | 67 | | 10| 100 | 89285 | 68 | | 100| 100 | 23255 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /super_timer.go: -------------------------------------------------------------------------------- 1 | package gtimer 2 | 3 | import ( 4 | "container/heap" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | //nolint: govet 10 | type SuperTimer struct { 11 | Wgp *sync.WaitGroup 12 | lock *sync.RWMutex 13 | UniTimer *time.Timer 14 | WorkerCount int 15 | ExitChan chan int 16 | PQ PriorityQueue 17 | RunningFlag *AtomicBool 18 | } 19 | 20 | func NewSuperTimer(workCount int) *SuperTimer { 21 | timer := SuperTimer{} 22 | timer.lock = &sync.RWMutex{} 23 | timer.UniTimer = time.NewTimer(time.Second * 10) 24 | timer.PQ = make(PriorityQueue, 0, 100) 25 | timer.WorkerCount = workCount 26 | timer.Wgp = &sync.WaitGroup{} 27 | timer.ExitChan = make(chan int, 100) 28 | timer.RunningFlag = NewAtomicBool(true) 29 | 30 | for i := 0; i < timer.WorkerCount; i++ { 31 | go timer.Consume() 32 | } 33 | return &timer 34 | } 35 | 36 | func (timer *SuperTimer) Consume() { 37 | timer.Wgp.Add(1) 38 | defer timer.Wgp.Done() 39 | for timer.RunningFlag.IsTrue() { 40 | select { 41 | case <-timer.ExitChan: 42 | break 43 | case <-timer.UniTimer.C: 44 | item := timer.Take() 45 | if item != nil { 46 | t := time.Unix(item.priority/int64(time.Second), item.priority%int64(time.Second)) 47 | item.OnTrigger(t, item.value) 48 | } 49 | } 50 | } 51 | } 52 | 53 | func (st *SuperTimer) Add(pItem *Item) { 54 | st.lock.Lock() 55 | defer st.lock.Unlock() 56 | heap.Push(&st.PQ, pItem) 57 | peek := st.PQ.Peek().(*Item) 58 | if peek == pItem { 59 | st.UniTimer.Reset(pItem.GetDelay()) 60 | } 61 | } 62 | 63 | func (st *SuperTimer) Remove(pItem *Item) { 64 | st.lock.Lock() 65 | defer st.lock.Unlock() 66 | st.PQ.Remove(pItem) 67 | } 68 | 69 | func (st *SuperTimer) Take() *Item { 70 | st.lock.Lock() 71 | defer st.lock.Unlock() 72 | if len(st.PQ) <= 0 { 73 | st.UniTimer.Reset(time.Second * 1) 74 | return nil 75 | } 76 | 77 | item := st.PQ.Peek() 78 | target := item.(*Item) 79 | if target.GetDelay() > 0 { 80 | st.UniTimer.Reset(target.GetDelay()) 81 | return nil 82 | } 83 | 84 | res := heap.Pop(&st.PQ).(*Item) 85 | // 重置定时器,立刻唤醒其它消费者 86 | st.UniTimer.Reset(0) 87 | return res 88 | } 89 | 90 | func (st *SuperTimer) Stop() { 91 | st.lock.Lock() 92 | defer st.lock.Unlock() 93 | st.PQ.Clear() 94 | st.RunningFlag.Set(false) 95 | close(st.ExitChan) 96 | st.UniTimer.Stop() 97 | } 98 | 99 | func (st *SuperTimer) Wait() { 100 | st.Wgp.Wait() 101 | } 102 | 103 | func (st *SuperTimer) Size() int { 104 | st.lock.RLock() 105 | defer st.lock.RUnlock() 106 | 107 | return len(st.PQ) 108 | } 109 | --------------------------------------------------------------------------------