background
- In Go, we need to start a goroutine to achieve timeout, but when I have a lot of tasks that require timeout control, I need to start a lot of goroutine, which is actually a kind of overhead and burden!
- Sometimes when you need to register some Timers, you also need to build a large number of goroutines to achieve it. For example, I need to refresh a configuration asynchronously and listen to something asynchronously. At this time, the simple way is to use a large number of goroutines + timer/sleep to implement it!
Solution
MultiplexingIn fact, Go is also a timer implemented by multiplexing ideas, but it is the timer at the bottom, and there are too many timer problems that we need to solve!
Our idea is to implement a TimerController to help us manage many times and minimize overhead! Therefore, it can be achieved using a small top heap + Timer scheduler!
accomplish
Small top heap (minimum heap)
Use Go's owncontainer/heap
Implementation Small Top Heap
import ( "container/heap" ) type HeapItem[T any] interface { Less(HeapItem[T]) bool GetValue() T } // Reference IntHeaptype heapQueue[T any] []HeapItem[T] func (h heapQueue[T]) Len() int { return len(h) } func (h heapQueue[T]) Less(i, j int) bool { return h[i].Less(h[j]) } func (h heapQueue[T]) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *heapQueue[T]) Push(x any) { // Push and Pop use pointer receivers because they modify the slice's length, // not just its contents. *h = append(*h, x.(HeapItem[T])) } func (h *heapQueue[T]) Pop() any { old := *h n := len(old) x := old[n-1] *h = old[0 : n-1] return x } type HeapQueue[T any] struct { queue heapQueue[T] } func (h *HeapQueue[T]) ptr() *heapQueue[T] { return & } // NewHeapQueue is non-concurrency securityfunc NewHeapQueue[T any](items ...HeapItem[T]) *HeapQueue[T] { queue := make(heapQueue[T], len(items)) for index, item := range items { queue[index] = item } (&queue) return &HeapQueue[T]{queue: queue} } func (h *HeapQueue[T]) Push(item HeapItem[T]) { ((), item) } func (h *HeapQueue[T]) Pop() (T, bool ) { if ().Len() == 0 { var Nil T return Nil, false } return (()).(HeapItem[T]).GetValue(), true } // The Peek method is used to return the top element of the heap without removing itfunc (h *HeapQueue[T]) Peek() (T, bool ) { if ().Len() > 0 { return [0].GetValue(), true } var Nil T return Nil, false } func (h *HeapQueue[T]) Len() int { return ().Len() }
Scheduler
type Timer struct { Timeout Name string NotifyFunc func() } func (t *Timer) GetCurTimeout() { return (()) } // Notify todo support async notify func (t *Timer) Notify() { if != nil { () } } func (t *Timer) IsExpired() bool { return (()) } func (t *Timer) Less(v HeapItem[*Timer]) bool { return (().Timeout) } func (t *Timer) GetValue() *Timer { return t } type TimerController struct { timers chan *Timer minHeap *HeapQueue[*Timer] closeOnce close chan struct{} } func (t *TimerController) AddTimer(timer *Timer) bool { if timer == nil { return false } select { case <-: return false default: <- timer return true } } func (t *TimerController) Close() { (func() { close() }) } func NewTimerController(bufferSize int) *TimerController { return &TimerController{ timers: make(chan *Timer, bufferSize), minHeap: NewHeapQueue[*Timer](), close: make(chan struct{}), } } func (t *TimerController) Start() { go t._start() } func (t *TimerController) _start() { const defaultTimeout = * 24 var ( curMinTimer *Timer timeout = (defaultTimeout) ) for { select { case <-: close() () return case timer := <-: (timer) curMinTimer, _ = () (()) //("-1 name: %s, timeout: %s\n", , ()) case <-: if curMinTimer != nil { () curMinTimer = nil () } curMinTimer, _ = () if curMinTimer == nil { (defaultTimeout) continue } (()) //("-2 name: %s, timeout: %s\n", , ()) } } }
test
func TestTimerController(t *) { controller := NewTimerController(1024) () defer () now := () arrs := make([]string, 0) NewTimer := func(num int) *Timer { return &Timer{Timeout: ((num) * ), Name: (num), NotifyFunc: func() { arrs = append(arrs, (num)) }} } // 8 timeser registered out of order here (NewTimer(5)) (NewTimer(6)) (NewTimer(3)) (NewTimer(4)) (NewTimer(7)) (NewTimer(8)) (NewTimer(1)) (NewTimer(2)) ( * 1) ("%#v\n", arrs) // Finally we can get the sequential execution! (t, arrs, []string{"1", "2", "3", "4", "5", "6", "7", "8"}) } func TestTimerController_Stable(t *) { controller := NewTimerController(1024) () defer () now := () arrs := make(map[string]bool, 0) NewTimer := func(num int, name string) *Timer { return &Timer{Timeout: ((num) * ), Name: name, NotifyFunc: func() { arrs[name] = true }} } // We repeatedly register the timer for the same implementation execution, so the expected result and registration order of each execution are the same as that of the registration. (NewTimer(2, "1")) (NewTimer(2, "2")) (NewTimer(2, "3")) (NewTimer(2, "4")) (NewTimer(2, "5")) ( * 1) ("%#v\n", arrs) (t, arrs, map[string]bool{"1": true, "2": true, "3": true, "4": true, "5": true}) }
The above is the detailed content of Go using TimerController to solve the problem of too many timers. For more information about Go TimerController to solve the problem of too many timers, please pay attention to my other related articles!