github.com/sandwich-go/boost@v1.3.29/xtime/wheel.go (about)

     1  // wheel implements a time wheel algorithm that is suitable for large numbers of timers.
     2  // It is based on golang channel broadcast mechanism.
     3  
     4  package xtime
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  )
    10  
    11  var (
    12  	timerMap = NewWheelMap()
    13  	accuracy = 20 // means max 1/20 deviation
    14  )
    15  
    16  func newWheelWithDuration(t time.Duration) *Wheel {
    17  	return NewWheel(t/time.Duration(accuracy), accuracy+1)
    18  }
    19  
    20  // WheelAfter 根据Duration复用时间轮
    21  // Note:
    22  //      内部会根据Duration创建时间轮, 相同Duration可以共用,这样带来的副作用就是如果时间不固定则会创建特别的多的时间轮
    23  func WheelAfter(t time.Duration) <-chan struct{} {
    24  	w, _ := timerMap.GetOrSetFuncLock(t, newWheelWithDuration)
    25  	return w.After(t)
    26  }
    27  
    28  // SetAccuracy sets the accuracy for the timewheel.
    29  // low accuracy usually have better performance.
    30  func SetAccuracy(a int) {
    31  	accuracy = a
    32  }
    33  
    34  type Wheel struct {
    35  	sync.Mutex
    36  	interval   time.Duration
    37  	ticker     *time.Ticker
    38  	quit       chan struct{}
    39  	maxTimeout time.Duration
    40  	cs         []chan struct{}
    41  	pos        int
    42  }
    43  
    44  func NewWheel(interval time.Duration, buckets int) *Wheel {
    45  	w := &Wheel{
    46  		interval:   interval,
    47  		quit:       make(chan struct{}),
    48  		pos:        0,
    49  		maxTimeout: interval * (time.Duration(buckets - 1)),
    50  		cs:         make([]chan struct{}, buckets),
    51  		ticker:     time.NewTicker(interval),
    52  	}
    53  	for i := range w.cs {
    54  		w.cs[i] = make(chan struct{})
    55  	}
    56  	go w.run()
    57  	return w
    58  }
    59  
    60  func (w *Wheel) Stop() {
    61  	close(w.quit)
    62  }
    63  
    64  // After 误差在一个interval内
    65  // timeline : ---w.pos-1<--{x}-->call After()<--{y}-->w.pos-----
    66  // x + y == interval, y 即是误差
    67  // Note:
    68  //      如果超过时间轮的最大值则使用最大值作为Timeout时间
    69  func (w *Wheel) After(timeout time.Duration) <-chan struct{} {
    70  	if timeout > w.maxTimeout {
    71  		timeout = w.maxTimeout
    72  	} else if timeout < time.Second {
    73  		timeout = time.Second
    74  	}
    75  
    76  	w.Lock()
    77  	index := (w.pos + int(timeout/w.interval)) % len(w.cs)
    78  	b := w.cs[index]
    79  	w.Unlock()
    80  	return b
    81  }
    82  
    83  func (w *Wheel) run() {
    84  	for {
    85  		select {
    86  		case <-w.ticker.C:
    87  			w.onTicker()
    88  		case <-w.quit:
    89  			w.ticker.Stop()
    90  			return
    91  		}
    92  	}
    93  }
    94  
    95  func (w *Wheel) onTicker() {
    96  	w.Lock()
    97  	lastC := w.cs[w.pos]
    98  	w.cs[w.pos] = make(chan struct{})
    99  	w.pos = (w.pos + 1) % len(w.cs)
   100  	w.Unlock()
   101  	close(lastC)
   102  }