基于close channel广播机制来实现TimingWheel
基于1个Ticker+1个环形数组+多个channel,实现了多个任务在指定最大超时时间范围内的一次性超时通知机制。
代码
package main
import (
"fmt"
"sync"
"time"
)
type TimingWheel struct {
sync.Mutex
interval time.Duration
ticker *time.Ticker
quit chan struct{}
maxTimeout time.Duration
cs []chan struct{}
pos int
}
func NewTimingWheel(interval time.Duration, buckets int) *TimingWheel {
w := new(TimingWheel)
w.interval = interval
w.quit = make(chan struct{})
w.pos = 0
w.maxTimeout = interval * (time.Duration(buckets))
// 初始化buckets个非缓冲channel
w.cs = make([]chan struct{}, buckets)
for i := range w.cs {
w.cs[i] = make(chan struct{})
}
w.ticker = time.NewTicker(interval)
go w.run()
return w
}
func (w *TimingWheel) Stop() {
close(w.quit)
}
func (w *TimingWheel) After(timeout time.Duration) <-chan struct{} {
// 不允许timeout>=非缓冲通道数组长度
if timeout >= w.maxTimeout {
panic("timeout too much, over maxtimeout")
}
// 3/1=3,返回下标是2的非缓冲通道
index := int(timeout / w.interval)
if index > 0 {
index--
}
// 在环形数组中根据pos来确定新下标
w.Lock()
index = (w.pos + index) % len(w.cs)
b := w.cs[index]
w.Unlock()
return b
}
func (w *TimingWheel) run() {
for {
select {
case <-w.ticker.C:
w.onTicker()
case <-w.quit:
w.ticker.Stop()
return
}
}
}
// 关闭老channel来触发广播,创建新channel
func (w *TimingWheel) onTicker() {
w.Lock()
lastCh := w.cs[w.pos]
w.cs[w.pos] = make(chan struct{})
w.pos = (w.pos + 1) % len(w.cs)
w.Unlock()
close(lastCh)
}
func main() {
// 最大超时时间是10s
w := NewTimingWheel(time.Second, 5)
fmt.Println("start time is ", time.Unix(time.Now().Unix(), 0))
// 单协程场景
select {
// 因为Ticker按照interval来触发,所以等待时间必须是间隔时间的整数倍
case <-w.After(3 * time.Second):
fmt.Println("end time is ", time.Unix(time.Now().Unix(), 0))
}
// 多协程场景
for i := 0; i < 3; i++ {
go func() {
select {
case <-w.After(3 * time.Second):
fmt.Println("end time is ", time.Unix(time.Now().Unix(), 0))
}
}()
}
time.Sleep(5 * time.Second)
}
执行结果
start time is 2022-11-20 20:14:46 +0800 CST
end time is 2022-11-20 20:14:49 +0800 CST
end time is 2022-11-20 20:14:52 +0800 CST
end time is 2022-11-20 20:14:52 +0800 CST
end time is 2022-11-20 20:14:52 +0800 CST