``` feat(pet): 重构宠物繁殖系统,添加蛋孵化功能

This commit is contained in:
1
2026-01-20 22:08:36 +00:00
parent cf4660fbe0
commit 5ef922278a
68 changed files with 4467 additions and 584 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2021 蚂蚁实验室
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,130 @@
## timer
[![Go](https://github.com/antlabs/timer/workflows/Go/badge.svg)](https://github.com/antlabs/timer/actions)
[![codecov](https://codecov.io/gh/antlabs/timer/branch/master/graph/badge.svg)](https://codecov.io/gh/antlabs/timer)
timer是高性能定时器库
## feature
* 支持一次性定时器
* 支持周期性定时器
* 支持多种数据结构后端最小堆5级时间轮
## 一次性定时器
```go
import (
"github.com/antlabs/timer"
"log"
)
func main() {
tm := timer.NewTimer()
tm.AfterFunc(1*time.Second, func() {
log.Printf("after\n")
})
tm.AfterFunc(10*time.Second, func() {
log.Printf("after\n")
})
tm.Run()
}
```
## 周期性定时器
```go
import (
"github.com/antlabs/timer"
"log"
)
func main() {
tm := timer.NewTimer()
tm.ScheduleFunc(1*time.Second, func() {
log.Printf("schedule\n")
})
tm.Run()
}
```
## 自定义周期性定时器
实现时间翻倍定时的例子
```go
type curstomTest struct {
count int
}
// 只要实现Next接口就行
func (c *curstomTest) Next(now time.Time) (rv time.Time) {
rv = now.Add(time.Duration(c.count) * time.Millisecond * 10)
c.count++
return
}
func main() {
tm := timer.NewTimer(timer.WithMinHeap())
node := tm.CustomFunc(&curstomTest{count: 1}, func() {
log.Printf("%v\n", time.Now())
})
tm.Run()
}
```
## 取消某一个定时器
```go
import (
"log"
"time"
"github.com/antlabs/timer"
)
func main() {
tm := timer.NewTimer()
// 只会打印2 time.Second
tm.AfterFunc(2*time.Second, func() {
log.Printf("2 time.Second")
})
// tk3 会被 tk3.Stop()函数调用取消掉
tk3 := tm.AfterFunc(3*time.Second, func() {
log.Printf("3 time.Second")
})
tk3.Stop() //取消tk3
tm.Run()
}
```
## 选择不同的的数据结构
```go
import (
"github.com/antlabs/timer"
"log"
)
func main() {
tm := timer.NewTimer(timer.WithMinHeap())// 选择最小堆,默认时间轮
}
```
## benchmark
github.com/antlabs/timer 性能最高
```
goos: linux
goarch: amd64
pkg: benchmark
Benchmark_antlabs_Timer_AddTimer/N-1m-16 9177537 124 ns/op
Benchmark_antlabs_Timer_AddTimer/N-5m-16 10152950 128 ns/op
Benchmark_antlabs_Timer_AddTimer/N-10m-16 9955639 127 ns/op
Benchmark_RussellLuo_Timingwheel_AddTimer/N-1m-16 5316916 222 ns/op
Benchmark_RussellLuo_Timingwheel_AddTimer/N-5m-16 5848843 218 ns/op
Benchmark_RussellLuo_Timingwheel_AddTimer/N-10m-16 5872621 231 ns/op
Benchmark_ouqiang_Timewheel/N-1m-16 720667 1622 ns/op
Benchmark_ouqiang_Timewheel/N-5m-16 807018 1573 ns/op
Benchmark_ouqiang_Timewheel/N-10m-16 666183 1557 ns/op
Benchmark_Stdlib_AddTimer/N-1m-16 8031864 144 ns/op
Benchmark_Stdlib_AddTimer/N-5m-16 8437442 151 ns/op
Benchmark_Stdlib_AddTimer/N-10m-16 8080659 167 ns/op
```
* 压测代码位于
https://github.com/junelabs/timer-benchmark

View File

@@ -0,0 +1 @@
go build -race long-time-test.go

View File

@@ -0,0 +1,157 @@
package main
import (
"log"
"sync"
"time"
"github.com/antlabs/timer"
)
// 这是一个长时间测试代码
// 测试周期执行
func schedule(tm timer.Timer) {
tm.ScheduleFunc(200*time.Millisecond, func() {
log.Printf("schedule 200 milliseconds\n")
})
tm.ScheduleFunc(time.Second, func() {
log.Printf("schedule second\n")
})
tm.ScheduleFunc(1*time.Minute, func() {
log.Printf("schedule minute\n")
})
tm.ScheduleFunc(1*time.Hour, func() {
log.Printf("schedule hour\n")
})
tm.ScheduleFunc(24*time.Hour, func() {
log.Printf("schedule day\n")
})
}
// 测试一次性定时器
func after(tm timer.Timer) {
var wg sync.WaitGroup
wg.Add(4)
defer wg.Wait()
go func() {
defer wg.Done()
for i := 0; i < 3600*24; i++ {
i := i
tm.AfterFunc(time.Second, func() {
log.Printf("after second:%d\n", i)
})
time.Sleep(900 * time.Millisecond)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 60*24; i++ {
i := i
tm.AfterFunc(time.Minute, func() {
log.Printf("after minute:%d\n", i)
})
time.Sleep(50 * time.Second)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 24; i++ {
i := i
tm.AfterFunc(time.Hour, func() {
log.Printf("after hour:%d\n", i)
})
time.Sleep(59 * time.Minute)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 1; i++ {
i := i
tm.AfterFunc(24*time.Hour, func() {
log.Printf("after day:%d\n", i)
})
time.Sleep(59 * time.Minute)
}
}()
}
// 检测 stop after 消息,没有打印是正确的行为
func stopNode(tm timer.Timer) {
var wg sync.WaitGroup
wg.Add(4)
defer wg.Wait()
go func() {
defer wg.Done()
for i := 0; i < 3600*24; i++ {
i := i
node := tm.AfterFunc(time.Second, func() {
log.Printf("stop after second:%d\n", i)
})
time.Sleep(900 * time.Millisecond)
node.Stop()
}
}()
go func() {
defer wg.Done()
for i := 0; i < 60*24; i++ {
i := i
node := tm.AfterFunc(time.Minute, func() {
log.Printf("stop after minute:%d\n", i)
})
time.Sleep(50 * time.Second)
node.Stop()
}
}()
go func() {
defer wg.Done()
for i := 0; i < 24; i++ {
i := i
node := tm.AfterFunc(time.Hour, func() {
log.Printf("stop after hour:%d\n", i)
})
time.Sleep(59 * time.Minute)
node.Stop()
}
}()
go func() {
defer wg.Done()
for i := 0; i < 1; i++ {
i := i
node := tm.AfterFunc(23*time.Hour, func() {
log.Printf("stop after day:%d\n", i)
})
time.Sleep(22 * time.Hour)
node.Stop()
}
}()
}
func main() {
log.SetFlags(log.Ldate | log.Lmicroseconds)
tm := timer.NewTimer()
go schedule(tm)
go after(tm)
go stopNode(tm)
go func() {
time.Sleep(time.Hour*24 + time.Hour)
tm.Stop()
}()
tm.Run()
}

View File

@@ -0,0 +1,5 @@
module github.com/antlabs/timer
go 1.19
require github.com/antlabs/stl v0.0.2

View File

@@ -0,0 +1,2 @@
github.com/antlabs/stl v0.0.2 h1:sna1AXR5yIkNE9lWhCcKbheFJSVfCa3vugnGyakI79s=
github.com/antlabs/stl v0.0.2/go.mod h1:kKrO4xrn9cfS1mJVo+/BqePZjAYMXqD0amGF2Ouq7ac=

View File

@@ -0,0 +1,221 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import (
"container/heap"
"context"
"sync"
"sync/atomic"
"time"
"github.com/panjf2000/ants/v2"
)
var _ Timer = (*minHeap)(nil)
var defaultTimeout = time.Hour
type minHeap struct {
mu sync.Mutex
minHeaps
chAdd chan struct{}
ctx context.Context
cancel context.CancelFunc
wait sync.WaitGroup
tm *time.Timer
runCount int32 // 单元测试时使用
}
// 一次性定时器
func (m *minHeap) AfterFunc(expire time.Duration, callback func()) TimeNoder {
return m.addCallback(expire, nil, callback, false)
}
// 周期性定时器
func (m *minHeap) ScheduleFunc(expire time.Duration, callback func()) TimeNoder {
return m.addCallback(expire, nil, callback, true)
}
// 自定义下次的时间
func (m *minHeap) CustomFunc(n Next, callback func()) TimeNoder {
return m.addCallback(time.Duration(0), n, callback, true)
}
// 加任务
func (m *minHeap) addCallback(expire time.Duration, n Next, callback func(), isSchedule bool) TimeNoder {
select {
case <-m.ctx.Done():
panic("cannot add a task to a closed timer")
default:
}
node := minHeapNode{
callback: callback,
userExpire: expire,
next: n,
absExpire: time.Now().Add(expire),
isSchedule: isSchedule,
root: m,
}
if n != nil {
node.absExpire = n.Next(time.Now())
}
m.mu.Lock()
heap.Push(&m.minHeaps, &node)
m.wait.Add(1)
m.mu.Unlock()
select {
case m.chAdd <- struct{}{}:
default:
}
return &node
}
func (m *minHeap) removeTimeNode(node *minHeapNode) {
m.mu.Lock()
if node.index < 0 || node.index > int32(len(m.minHeaps)) || int32(len(m.minHeaps)) == 0 {
m.mu.Unlock()
return
}
heap.Remove(&m.minHeaps, int(node.index))
m.wait.Done()
m.mu.Unlock()
}
func (m *minHeap) resetTimeNode(node *minHeapNode, d time.Duration) {
m.mu.Lock()
node.userExpire = d
node.absExpire = time.Now().Add(d)
heap.Fix(&m.minHeaps, int(node.index))
select {
case m.chAdd <- struct{}{}:
default:
}
m.mu.Unlock()
}
func (m *minHeap) getNewSleepTime() time.Duration {
if m.minHeaps.Len() == 0 {
return time.Hour
}
timeout := time.Until(m.minHeaps[0].absExpire)
if timeout < 0 {
timeout = 0
}
return timeout
}
var pool, _ = ants.NewPool(-1)
func (m *minHeap) process() {
for {
m.mu.Lock()
now := time.Now()
// 如果堆中没有元素,就等待
// 这时候设置一个相对长的时间避免空转cpu
if m.minHeaps.Len() == 0 {
m.tm.Reset(time.Hour)
m.mu.Unlock()
return
}
for {
// 取出最小堆的第一个元素
first := m.minHeaps[0]
// 时间未到直接过滤掉
// 只是跳过最近的循环
if !now.After(first.absExpire) {
break
}
// 取出待执行的callback
callback := first.callback
// 如果是周期性任务
if first.isSchedule {
// 计算下次触发的绝对时间点
first.absExpire = first.Next(now)
// 修改下在堆中的位置
heap.Fix(&m.minHeaps, int(first.index))
} else {
// 从堆中删除
heap.Pop(&m.minHeaps)
m.wait.Done()
}
// 正在运行的任务数加1
atomic.AddInt32(&m.runCount, 1)
pool.Submit(func() {
callback()
// 对正在运行的任务数减1
atomic.AddInt32(&m.runCount, -1)
})
// 如果堆中没有元素,就等待
if m.minHeaps.Len() == 0 {
m.tm.Reset(defaultTimeout)
m.mu.Unlock()
return
}
}
// 取出第一个元素
first := m.minHeaps[0]
// 如果第一个元素的时间还没到,就计算下次触发的时间
if time.Now().Before(first.absExpire) {
to := m.getNewSleepTime()
m.tm.Reset(to)
// fmt.Printf("### now=%v, to = %v, m.minHeaps[0].absExpire = %v\n", time.Now(), to, m.minHeaps[0].absExpire)
m.mu.Unlock()
return
}
m.mu.Unlock()
}
}
// 运行
// 为了避免空转cpu, 会等待一个chan, 只要AfterFunc或者ScheduleFunc被调用就会往这个chan里面写值
func (m *minHeap) Run() {
m.tm = time.NewTimer(time.Hour)
m.process()
for {
select {
case <-m.tm.C:
m.process()
case <-m.chAdd:
m.mu.Lock()
// 极端情况,加完任务立即给删除了, 判断下当前堆中是否有元素
if m.minHeaps.Len() > 0 {
m.tm.Reset(m.getNewSleepTime())
}
m.mu.Unlock()
// 进入事件循环,如果为空就会从事件循环里面退出
case <-m.ctx.Done():
// 等待所有任务结束
m.wait.Wait()
return
}
}
}
// 停止所有定时器
func (m *minHeap) Stop() {
m.cancel()
}
func newMinHeap() (mh *minHeap) {
mh = &minHeap{}
heap.Init(&mh.minHeaps)
mh.chAdd = make(chan struct{}, 1024)
mh.ctx, mh.cancel = context.WithCancel(context.TODO())
return
}

View File

@@ -0,0 +1,62 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import (
"time"
)
type minHeapNode struct {
callback func() // 用户的callback
absExpire time.Time // 绝对时间
userExpire time.Duration // 过期时间段
root *minHeap // 指向最小堆
next Next // 自定义下个触发的时间点, cronex项目用到了
index int32 // 在min heap中的索引方便删除或者重新推入堆中
isSchedule bool // 是否是周期性任务
}
func (m *minHeapNode) Stop() bool {
m.root.removeTimeNode(m)
return true
}
func (m *minHeapNode) Reset(d time.Duration) bool {
m.root.resetTimeNode(m, d)
return true
}
func (m *minHeapNode) Next(now time.Time) time.Time {
if m.next != nil {
return (m.next).Next(now)
}
return now.Add(m.userExpire)
}
type minHeaps []*minHeapNode
func (m minHeaps) Len() int { return len(m) }
func (m minHeaps) Less(i, j int) bool { return m[i].absExpire.Before(m[j].absExpire) }
func (m minHeaps) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
m[i].index = int32(i)
m[j].index = int32(j)
}
func (m *minHeaps) Push(x any) {
// Push and Pop use pointer receivers because they modify the slice's length,
// not just its contents.
*m = append(*m, x.(*minHeapNode))
lastIndex := int32(len(*m) - 1)
(*m)[lastIndex].index = lastIndex
}
func (m *minHeaps) Pop() any {
old := *m
n := len(old)
x := old[n-1]
*m = old[0 : n-1]
return x
}

View File

@@ -0,0 +1,64 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import (
"container/heap"
"testing"
"time"
)
func Test_NodeSizeof(t *testing.T) {
t.Run("输出最小堆node的sizeof", func(t *testing.T) {
// t.Logf("minHeapNode size: %d %d\n", unsafe.Sizeof(minHeapNode{}), unsafe.Sizeof(time.Timer{}))
})
}
func Test_MinHeap(t *testing.T) {
t.Run("", func(t *testing.T) {
var mh minHeaps
now := time.Now()
n1 := minHeapNode{
absExpire: now.Add(time.Second),
userExpire: 1 * time.Second,
}
n2 := minHeapNode{
absExpire: now.Add(2 * time.Second),
userExpire: 2 * time.Second,
}
n3 := minHeapNode{
absExpire: now.Add(3 * time.Second),
userExpire: 3 * time.Second,
}
n6 := minHeapNode{
absExpire: now.Add(6 * time.Second),
userExpire: 6 * time.Second,
}
n5 := minHeapNode{
absExpire: now.Add(5 * time.Second),
userExpire: 5 * time.Second,
}
n4 := minHeapNode{
absExpire: now.Add(4 * time.Second),
userExpire: 4 * time.Second,
}
mh.Push(&n1)
mh.Push(&n2)
mh.Push(&n3)
mh.Push(&n6)
mh.Push(&n5)
mh.Push(&n4)
for i := 1; len(mh) > 0; i++ {
v := heap.Pop(&mh).(*minHeapNode)
if v.userExpire != time.Duration(i)*time.Second {
t.Errorf("index(%d) v.userExpire(%v) != %v", i, v.userExpire, time.Duration(i)*time.Second)
}
}
})
}

View File

@@ -0,0 +1,329 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import (
"log"
"sync"
"sync/atomic"
"testing"
"time"
)
// 测试AfterFunc有没有运行以及时间间隔可对
func Test_MinHeap_AfterFunc_Run(t *testing.T) {
t.Run("1ms", func(t *testing.T) {
tm := NewTimer(WithMinHeap())
go tm.Run()
count := int32(0)
tc := make(chan time.Duration, 2)
var mu sync.Mutex
isClose := false
now := time.Now()
node1 := tm.AfterFunc(time.Millisecond, func() {
mu.Lock()
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) <= 2 && !isClose {
tc <- time.Since(now)
}
mu.Unlock()
})
node2 := tm.AfterFunc(time.Millisecond, func() {
mu.Lock()
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) <= 2 && !isClose {
tc <- time.Since(now)
}
mu.Unlock()
})
time.Sleep(time.Millisecond * 3)
mu.Lock()
isClose = true
close(tc)
node1.Stop()
node2.Stop()
mu.Unlock()
for tv := range tc {
if tv < time.Millisecond || tv > 2*time.Millisecond {
t.Errorf("tc < time.Millisecond tc > 2*time.Millisecond")
}
}
if atomic.LoadInt32(&count) != 2 {
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
}
})
t.Run("10ms", func(t *testing.T) {
tm := NewTimer(WithMinHeap())
go tm.Run() // 运行事件循环
count := int32(0)
tc := make(chan time.Duration, 2)
var mu sync.Mutex
isClosed := false
now := time.Now()
node1 := tm.AfterFunc(time.Millisecond*10, func() {
now2 := time.Now()
mu.Lock()
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) <= 2 && !isClosed {
tc <- time.Since(now)
}
mu.Unlock()
log.Printf("node1.Lock:%v\n", time.Since(now2))
})
node2 := tm.AfterFunc(time.Millisecond*10, func() {
now2 := time.Now()
mu.Lock()
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) <= 2 && !isClosed {
tc <- time.Since(now)
}
mu.Unlock()
log.Printf("node2.Lock:%v\n", time.Since(now2))
})
time.Sleep(time.Millisecond * 24)
now3 := time.Now()
mu.Lock()
node1.Stop()
node2.Stop()
isClosed = true
close(tc)
mu.Unlock()
log.Printf("node1.Stop:%v\n", time.Since(now3))
cnt := 1
for tv := range tc {
left := time.Millisecond * 10 * time.Duration(cnt)
right := time.Duration(cnt) * 2 * 10 * time.Millisecond
if tv < left || tv > right {
t.Errorf("index(%d) (%v)tc < %v || tc > %v", cnt, tv, left, right)
}
// cnt++
}
if atomic.LoadInt32(&count) != 2 {
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
}
})
t.Run("90ms", func(t *testing.T) {
tm := NewTimer(WithMinHeap())
go tm.Run()
count := int32(0)
tm.AfterFunc(time.Millisecond*90, func() { atomic.AddInt32(&count, 1) })
tm.AfterFunc(time.Millisecond*90, func() { atomic.AddInt32(&count, 2) })
time.Sleep(time.Millisecond * 180)
if atomic.LoadInt32(&count) != 3 {
t.Errorf("count != 3")
}
})
}
// 测试Schedule 运行的周期可对
func Test_MinHeap_ScheduleFunc_Run(t *testing.T) {
t.Run("1ms", func(t *testing.T) {
tm := NewTimer(WithMinHeap())
go tm.Run()
count := int32(0)
_ = tm.ScheduleFunc(2*time.Millisecond, func() {
log.Printf("%v\n", time.Now())
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) == 2 {
tm.Stop()
}
})
time.Sleep(time.Millisecond * 5)
if atomic.LoadInt32(&count) != 2 {
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
}
})
t.Run("10ms", func(t *testing.T) {
tm := NewTimer(WithMinHeap())
go tm.Run()
count := int32(0)
tc := make(chan time.Duration, 2)
var mu sync.Mutex
isClosed := false
now := time.Now()
node := tm.ScheduleFunc(time.Millisecond*10, func() {
mu.Lock()
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) <= 2 && !isClosed {
tc <- time.Since(now)
}
mu.Unlock()
})
time.Sleep(time.Millisecond * 25)
mu.Lock()
close(tc)
isClosed = true
node.Stop()
mu.Unlock()
cnt := 1
for tv := range tc {
left := time.Millisecond * 10 * time.Duration(cnt)
right := time.Duration(cnt) * 2 * 10 * time.Millisecond
if tv < left || tv > right {
t.Errorf("index(%d) (%v)tc < %v || tc > %v", cnt, tv, left, right)
}
cnt++
}
if atomic.LoadInt32(&count) != 2 {
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
}
})
t.Run("30ms", func(t *testing.T) {
tm := NewTimer(WithMinHeap())
go tm.Run()
count := int32(0)
c := make(chan bool, 1)
node := tm.ScheduleFunc(time.Millisecond*30, func() {
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) == 2 {
c <- true
}
})
go func() {
<-c
node.Stop()
}()
time.Sleep(time.Millisecond * 70)
if atomic.LoadInt32(&count) != 2 {
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
}
})
}
// 测试Stop是否会等待正在运行的任务结束
func Test_Run_Stop(t *testing.T) {
t.Run("1ms", func(t *testing.T) {
tm := NewTimer(WithMinHeap())
count := uint32(0)
tm.AfterFunc(time.Millisecond, func() { atomic.AddUint32(&count, 1) })
tm.AfterFunc(time.Millisecond, func() { atomic.AddUint32(&count, 1) })
go func() {
time.Sleep(time.Millisecond * 4)
tm.Stop()
}()
tm.Run()
if atomic.LoadUint32(&count) != 2 {
t.Errorf("count != 2")
}
})
}
type curstomTest struct {
count int32
}
func (c *curstomTest) Next(now time.Time) (rv time.Time) {
rv = now.Add(time.Duration(c.count) * time.Millisecond * 10)
atomic.AddInt32(&c.count, 1)
return
}
// 验证自定义函数的运行间隔时间
func Test_CustomFunc(t *testing.T) {
t.Run("custom", func(t *testing.T) {
tm := NewTimer(WithMinHeap())
// mh := tm.(*minHeap) // 最小堆
tc := make(chan time.Duration, 2)
now := time.Now()
count := uint32(1)
stop := make(chan bool, 1)
// 自定义函数
node := tm.CustomFunc(&curstomTest{count: 1}, func() {
if atomic.LoadUint32(&count) == 2 {
return
}
// 计算运行次数
atomic.AddUint32(&count, 1)
tc <- time.Since(now)
// 关闭这个任务
close(stop)
})
go func() {
<-stop
node.Stop()
tm.Stop()
}()
tm.Run()
close(tc)
cnt := 1
for tv := range tc {
left := time.Millisecond * 10 * time.Duration(cnt)
right := time.Duration(cnt) * 2 * 10 * time.Millisecond
if tv < left || tv > right {
t.Errorf("index(%d) (%v)tc < %v || tc > %v", cnt, tv, left, right)
}
cnt++
}
if atomic.LoadUint32(&count) != 2 {
t.Errorf("count != 2")
}
// 正在运行的任务是比较短暂的,所以外部很难
// if mh.runCount != int32(1) {
// t.Errorf("mh.runCount:%d != 1", mh.runCount)
// }
})
}
// 验证运行次数是符合预期的
func Test_RunCount(t *testing.T) {
t.Run("runcount-10ms", func(t *testing.T) {
tm := NewTimer(WithMinHeap())
max := 10
go func() {
tm.Run()
}()
count := uint32(0)
for i := 0; i < max; i++ {
tm.ScheduleFunc(time.Millisecond*10, func() {
atomic.AddUint32(&count, 1)
})
}
time.Sleep(time.Millisecond * 15)
tm.Stop()
if count != uint32(max) {
t.Errorf("count:%d != %d", count, max)
}
})
}

View File

@@ -0,0 +1,39 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
type option struct {
timeWheel bool
minHeap bool
skiplist bool
rbtree bool
}
type Option func(c *option)
func WithTimeWheel() Option {
return func(o *option) {
o.timeWheel = true
}
}
func WithMinHeap() Option {
return func(o *option) {
o.minHeap = true
}
}
// TODO
func WithSkipList() Option {
return func(o *option) {
o.skiplist = true
}
}
// TODO
func WithRbtree() Option {
return func(o *option) {
o.rbtree = true
}
}

View File

@@ -0,0 +1,17 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import (
"fmt"
"testing"
"unsafe"
)
func Test_Look(t *testing.T) {
tmp := newTimeHead(0, 0)
offset := unsafe.Offsetof(tmp.Head)
fmt.Printf("%d\n", offset)
}

View File

@@ -0,0 +1,294 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import (
"context"
"fmt"
"sync/atomic"
"time"
"unsafe"
"github.com/antlabs/stl/list"
)
const (
nearShift = 8
nearSize = 1 << nearShift
levelShift = 6
levelSize = 1 << levelShift
nearMask = nearSize - 1
levelMask = levelSize - 1
)
type timeWheel struct {
// 单调递增累加值, 走过一个时间片就+1
jiffies uint64
// 256个槽位
t1 [nearSize]*Time
// 4个64槽位, 代表不同的刻度
t2Tot5 [4][levelSize]*Time
// 时间只精确到10ms
// curTimePoint 为1就是10ms 为2就是20ms
curTimePoint time.Duration
// 上下文
ctx context.Context
// 取消函数
cancel context.CancelFunc
}
func newTimeWheel() *timeWheel {
ctx, cancel := context.WithCancel(context.Background())
t := &timeWheel{ctx: ctx, cancel: cancel}
t.init()
return t
}
func (t *timeWheel) init() {
for i := 0; i < nearSize; i++ {
t.t1[i] = newTimeHead(1, uint64(i))
}
for i := 0; i < 4; i++ {
for j := 0; j < levelSize; j++ {
t.t2Tot5[i][j] = newTimeHead(uint64(i+2), uint64(j))
}
}
// t.curTimePoint = get10Ms()
}
func maxVal() uint64 {
return (1 << (nearShift + 4*levelShift)) - 1
}
func levelMax(index int) uint64 {
return 1 << (nearShift + index*levelShift)
}
func (t *timeWheel) index(n int) uint64 {
return (t.jiffies >> (nearShift + levelShift*n)) & levelMask
}
func (t *timeWheel) add(node *timeNode, jiffies uint64) *timeNode {
var head *Time
expire := node.expire
idx := expire - jiffies
level, index := uint64(1), uint64(0)
if idx < nearSize {
index = uint64(expire) & nearMask
head = t.t1[index]
} else {
max := maxVal()
for i := 0; i <= 3; i++ {
if idx > max {
idx = max
expire = idx + jiffies
}
if uint64(idx) < levelMax(i+1) {
index = uint64(expire >> (nearShift + i*levelShift) & levelMask)
head = t.t2Tot5[i][index]
level = uint64(i) + 2
break
}
}
}
if head == nil {
panic("not found head")
}
head.lockPushBack(node, level, index)
return node
}
func (t *timeWheel) AfterFunc(expire time.Duration, callback func()) TimeNoder {
jiffies := atomic.LoadUint64(&t.jiffies)
expire = expire/(time.Millisecond*10) + time.Duration(jiffies)
node := &timeNode{
expire: uint64(expire),
callback: callback,
root: t,
}
return t.add(node, jiffies)
}
func getExpire(expire time.Duration, jiffies uint64) time.Duration {
return expire/(time.Millisecond*10) + time.Duration(jiffies)
}
func (t *timeWheel) ScheduleFunc(userExpire time.Duration, callback func()) TimeNoder {
jiffies := atomic.LoadUint64(&t.jiffies)
expire := getExpire(userExpire, jiffies)
node := &timeNode{
userExpire: userExpire,
expire: uint64(expire),
callback: callback,
isSchedule: true,
root: t,
}
return t.add(node, jiffies)
}
func (t *timeWheel) Stop() {
t.cancel()
}
// 移动链表
func (t *timeWheel) cascade(levelIndex int, index int) {
tmp := newTimeHead(0, 0)
l := t.t2Tot5[levelIndex][index]
l.Lock()
if l.Len() == 0 {
l.Unlock()
return
}
l.ReplaceInit(&tmp.Head)
// 每次链表的元素被移动走都修改version
l.version.Add(1)
l.Unlock()
offset := unsafe.Offsetof(tmp.Head)
tmp.ForEachSafe(func(pos *list.Head) {
node := (*timeNode)(pos.Entry(offset))
t.add(node, atomic.LoadUint64(&t.jiffies))
})
}
// moveAndExec函数功能
// 1. 先移动到near链表里面
// 2. near链表节点为空时从上一层里面移动一些节点到下一层
// 3. 再执行
func (t *timeWheel) moveAndExec() {
// 这里时间溢出
if uint32(t.jiffies) == 0 {
// TODO
// return
}
// 如果本层的盘子没有定时器,这时候从上层的盘子移动一些过来
index := t.jiffies & nearMask
if index == 0 {
for i := 0; i <= 3; i++ {
index2 := t.index(i)
t.cascade(i, int(index2))
if index2 != 0 {
break
}
}
}
atomic.AddUint64(&t.jiffies, 1)
t.t1[index].Lock()
if t.t1[index].Len() == 0 {
t.t1[index].Unlock()
return
}
head := newTimeHead(0, 0)
t1 := t.t1[index]
t1.ReplaceInit(&head.Head)
t1.version.Add(1)
t.t1[index].Unlock()
// 执行,链表中的定时器
offset := unsafe.Offsetof(head.Head)
head.ForEachSafe(func(pos *list.Head) {
val := (*timeNode)(pos.Entry(offset))
head.Del(pos)
if val.stop.Load() == haveStop {
return
}
go val.callback()
if val.isSchedule {
jiffies := t.jiffies
// 这里的jiffies必须要减去1
// 当前的callback被调用已经包含一个时间片,如果不把这个时间片减去,
// 每次多一个时间片,就变成累加器, 最后周期定时器慢慢会变得不准
val.expire = uint64(getExpire(val.userExpire, jiffies-1))
t.add(val, jiffies)
}
})
}
// get10Ms函数通过参数传递为了方便测试
func (t *timeWheel) run(get10Ms func() time.Duration) {
// 先判断是否需要更新
// 内核里面实现使用了全局jiffies和本地的jiffies比较,应用层没有jiffies直接使用时间比较
// 这也是skynet里面的做法
ms10 := get10Ms()
if ms10 < t.curTimePoint {
fmt.Printf("github.com/antlabs/timer:Time has been called back?from(%d)(%d)\n",
ms10, t.curTimePoint)
t.curTimePoint = ms10
return
}
diff := ms10 - t.curTimePoint
t.curTimePoint = ms10
for i := 0; i < int(diff); i++ {
t.moveAndExec()
}
}
// 自定义, TODO
func (t *timeWheel) CustomFunc(n Next, callback func()) TimeNoder {
return &timeNode{}
}
func (t *timeWheel) Run() {
t.curTimePoint = get10Ms()
// 10ms精度
tk := time.NewTicker(time.Millisecond * 10)
defer tk.Stop()
for {
select {
case <-tk.C:
t.run(get10Ms)
case <-t.ctx.Done():
return
}
}
}

View File

@@ -0,0 +1,114 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import (
"sync"
"sync/atomic"
"time"
"unsafe"
"github.com/antlabs/stl/list"
)
const (
haveStop = uint32(1)
)
// 先使用sync.Mutex实现功能
// 后面使用cas优化
type Time struct {
timeNode
sync.Mutex
// |---16bit---|---16bit---|------32bit-----|
// |---level---|---index---|-------seq------|
// level 在near盘子里就是1, 在T2ToTt[0]盘子里就是2起步
// index 就是各自盘子的索引值
// seq 自增id
version atomic.Uint64
}
func newTimeHead(level uint64, index uint64) *Time {
head := &Time{}
head.version.Store(genVersionHeight(level, index))
head.Init()
return head
}
func genVersionHeight(level uint64, index uint64) uint64 {
return level<<(32+16) | index<<32
}
func (t *Time) lockPushBack(node *timeNode, level uint64, index uint64) {
t.Lock()
defer t.Unlock()
if node.stop.Load() == haveStop {
return
}
t.AddTail(&node.Head)
atomic.StorePointer(&node.list, unsafe.Pointer(t))
//更新节点的version信息
node.version.Store(t.version.Load())
}
type timeNode struct {
expire uint64
userExpire time.Duration
callback func()
stop atomic.Uint32
list unsafe.Pointer //存放表头信息
version atomic.Uint64 //保存节点版本信息
isSchedule bool
root *timeWheel
list.Head
}
// 一个timeNode节点有4个状态
// 1.存在于初始化链表中
// 2.被移动到tmp链表
// 3.1 和 3.2是if else的状态
//
// 3.1被移动到new链表
// 3.2直接执行
//
// 1和3.1状态是没有问题的
// 2和3.2状态会是没有锁保护下的操作,会有数据竞争
func (t *timeNode) Stop() bool {
t.stop.Store(haveStop)
// 使用版本号算法让timeNode知道自己是否被移动了
// timeNode的version和表头的version一样表示没有被移动可以直接删除
// 如果不一样可能在第2或者3.2状态,使用惰性删除
cpyList := (*Time)(atomic.LoadPointer(&t.list))
cpyList.Lock()
defer cpyList.Unlock()
if t.version.Load() != cpyList.version.Load() {
return false
}
cpyList.Del(&t.Head)
return true
}
// warning: 该函数目前没有稳定
func (t *timeNode) Reset(expire time.Duration) bool {
cpyList := (*Time)(atomic.LoadPointer(&t.list))
cpyList.Lock()
defer cpyList.Unlock()
// TODO: 这里有一个问题如果在执行Reset的时候这个节点已经被移动到tmp链表
// if atomic.LoadUint64(&t.version) != atomic.LoadUint64(&cpyList.version) {
// return
// }
cpyList.Del(&t.Head)
jiffies := atomic.LoadUint64(&t.root.jiffies)
expire = expire/(time.Millisecond*10) + time.Duration(jiffies)
t.expire = uint64(expire)
t.root.add(t, jiffies)
return true
}

View File

@@ -0,0 +1,189 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import (
"context"
"math"
"sync/atomic"
"testing"
"time"
)
func Test_maxVal(t *testing.T) {
if maxVal() != uint64(math.MaxUint32) {
t.Error("maxVal() != uint64(math.MaxUint32)")
}
}
func Test_LevelMax(t *testing.T) {
if levelMax(1) != uint64(1<<(nearShift+levelShift)) {
t.Error("levelMax(1) != uint64(1<<(nearShift+levelShift))")
}
if levelMax(2) != uint64(1<<(nearShift+2*levelShift)) {
t.Error("levelMax(2) != uint64(1<<(nearShift+2*levelShift))")
}
if levelMax(3) != uint64(1<<(nearShift+3*levelShift)) {
t.Error("levelMax(3) != uint64(1<<(nearShift+3*levelShift))")
}
if levelMax(4) != uint64(1<<(nearShift+4*levelShift)) {
t.Error("levelMax(4) != uint64(1<<(nearShift+4*levelShift))")
}
}
func Test_GenVersion(t *testing.T) {
if genVersionHeight(1, 0xf) != uint64(0x0001000f00000000) {
t.Error("genVersionHeight(1, 0xf) != uint64(0x0001000f00000000)")
}
if genVersionHeight(1, 64) != uint64(0x0001004000000000) {
t.Error("genVersionHeight(2, 0xf) != uint64(0x0001004000000000)")
}
}
// 测试1小时
func Test_hour(t *testing.T) {
tw := newTimeWheel()
testHour := new(bool)
done := make(chan struct{}, 1)
tw.AfterFunc(time.Hour, func() {
*testHour = true
done <- struct{}{}
})
expire := getExpire(time.Hour, 0)
for i := 0; i < int(expire)+10; i++ {
get10Ms := func() time.Duration {
return tw.curTimePoint + 1
}
tw.run(get10Ms)
}
select {
case <-done:
case <-time.After(time.Second / 100):
}
if *testHour == false {
t.Error("testHour == false")
}
}
// 测试周期性定时器, 5s
func Test_ScheduleFunc_5s(t *testing.T) {
tw := newTimeWheel()
var first5 int32
ctx, cancel := context.WithCancel(context.Background())
const total = int32(1000)
testTime := time.Second * 5
tw.ScheduleFunc(testTime, func() {
atomic.AddInt32(&first5, 1)
if atomic.LoadInt32(&first5) == total {
cancel()
}
})
expire := getExpire(testTime*time.Duration(total), 0)
for i := 0; i <= int(expire)+10; i++ {
get10Ms := func() time.Duration {
return tw.curTimePoint + 1
}
tw.run(get10Ms)
}
select {
case <-ctx.Done():
case <-time.After(time.Second / 100):
}
if total != first5 {
t.Errorf("total:%d != first5:%d\n", total, first5)
}
}
// 测试周期性定时器, 1hour
func Test_ScheduleFunc_hour(t *testing.T) {
tw := newTimeWheel()
var first5 int32
ctx, cancel := context.WithCancel(context.Background())
const total = int32(100)
testTime := time.Hour
tw.ScheduleFunc(testTime, func() {
atomic.AddInt32(&first5, 1)
if atomic.LoadInt32(&first5) == total {
cancel()
}
})
expire := getExpire(testTime*time.Duration(total), 0)
for i := 0; i <= int(expire)+10; i++ {
get10Ms := func() time.Duration {
return tw.curTimePoint + 1
}
tw.run(get10Ms)
}
select {
case <-ctx.Done():
case <-time.After(time.Second / 100):
}
if total != first5 {
t.Errorf("total:%d != first5:%d\n", total, first5)
}
}
// 测试周期性定时器, 1day
func Test_ScheduleFunc_day(t *testing.T) {
tw := newTimeWheel()
var first5 int32
ctx, cancel := context.WithCancel(context.Background())
const total = int32(10)
testTime := time.Hour * 24
tw.ScheduleFunc(testTime, func() {
atomic.AddInt32(&first5, 1)
if atomic.LoadInt32(&first5) == total {
cancel()
}
})
expire := getExpire(testTime*time.Duration(total), 0)
for i := 0; i <= int(expire)+10; i++ {
get10Ms := func() time.Duration {
return tw.curTimePoint + 1
}
tw.run(get10Ms)
}
select {
case <-ctx.Done():
case <-time.After(time.Second / 100):
}
if total != first5 {
t.Errorf("total:%d != first5:%d\n", total, first5)
}
}

View File

@@ -0,0 +1,10 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import "time"
func get10Ms() time.Duration {
return time.Duration(int64(time.Now().UnixNano() / int64(time.Millisecond) / 10))
}

View File

@@ -0,0 +1,53 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import "time"
type Next interface {
Next(time.Time) time.Time
}
// 定时器接口
type Timer interface {
// 一次性定时器
AfterFunc(expire time.Duration, callback func()) TimeNoder
// 周期性定时器
ScheduleFunc(expire time.Duration, callback func()) TimeNoder
// 自定义下次的时间
CustomFunc(n Next, callback func()) TimeNoder
// 运行
Run()
// 停止所有定时器
Stop()
}
// 停止单个定时器
type TimeNoder interface {
Stop() bool
// 重置时间器
Reset(expire time.Duration) bool
}
// 定时器构造函数
func NewTimer(opt ...Option) Timer {
var o option
for _, cb := range opt {
cb(&o)
}
if o.timeWheel {
return newTimeWheel()
}
if o.minHeap {
return newMinHeap()
}
return newTimeWheel()
}

View File

@@ -0,0 +1,219 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import (
"log"
"sync"
"sync/atomic"
"testing"
"time"
)
func Test_ScheduleFunc(t *testing.T) {
tm := NewTimer()
log.SetFlags(log.Ldate | log.Lmicroseconds)
count := uint32(0)
log.Printf("start\n")
tm.ScheduleFunc(time.Millisecond*100, func() {
log.Printf("schedule\n")
atomic.AddUint32(&count, 1)
})
go func() {
time.Sleep(570 * time.Millisecond)
log.Printf("stop\n")
tm.Stop()
}()
tm.Run()
if count != 5 {
t.Errorf("count:%d != 5\n", count)
}
}
func Test_AfterFunc(t *testing.T) {
tm := NewTimer()
go tm.Run()
log.Printf("start\n")
count := uint32(0)
tm.AfterFunc(time.Millisecond*20, func() {
log.Printf("after Millisecond * 20")
atomic.AddUint32(&count, 1)
})
tm.AfterFunc(time.Second, func() {
log.Printf("after second")
atomic.AddUint32(&count, 1)
})
/*
tm.AfterFunc(time.Minute, func() {
log.Printf("after Minute")
})
*/
/*
tm.AfterFunc(time.Hour, nil)
tm.AfterFunc(time.Hour*24, nil)
tm.AfterFunc(time.Hour*24*365, nil)
tm.AfterFunc(time.Hour*24*365*12, nil)
*/
time.Sleep(time.Second + time.Millisecond*100)
tm.Stop()
if count != 2 {
t.Errorf("count:%d != 2\n", count)
}
}
func Test_Node_Stop_1(t *testing.T) {
tm := NewTimer()
count := uint32(0)
node := tm.AfterFunc(time.Millisecond*10, func() {
atomic.AddUint32(&count, 1)
})
go func() {
time.Sleep(time.Millisecond * 30)
node.Stop()
tm.Stop()
}()
tm.Run()
if count != 1 {
t.Errorf("count:%d == 1\n", count)
}
}
func Test_Node_Stop(t *testing.T) {
tm := NewTimer()
count := uint32(0)
node := tm.AfterFunc(time.Millisecond*100, func() {
atomic.AddUint32(&count, 1)
})
node.Stop()
go func() {
time.Sleep(time.Millisecond * 200)
tm.Stop()
}()
tm.Run()
if count == 1 {
t.Errorf("count:%d == 1\n", count)
}
}
// 测试重置定时器
func Test_Reset(t *testing.T) {
t.Run("min heap reset", func(t *testing.T) {
tm := NewTimer(WithMinHeap())
go tm.Run()
count := int32(0)
tc := make(chan time.Duration, 2)
var mu sync.Mutex
isClose := false
now := time.Now()
node1 := tm.AfterFunc(time.Millisecond*100, func() {
mu.Lock()
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) <= 2 && !isClose {
tc <- time.Since(now)
}
mu.Unlock()
})
node2 := tm.AfterFunc(time.Millisecond*100, func() {
mu.Lock()
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) <= 2 && !isClose {
tc <- time.Since(now)
}
mu.Unlock()
})
node1.Reset(time.Millisecond)
node2.Reset(time.Millisecond)
time.Sleep(time.Millisecond * 3)
mu.Lock()
isClose = true
close(tc)
node1.Stop()
node2.Stop()
mu.Unlock()
for tv := range tc {
if tv < time.Millisecond || tv > 2*time.Millisecond {
t.Errorf("tc < time.Millisecond tc > 2*time.Millisecond")
}
}
if atomic.LoadInt32(&count) != 2 {
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
}
})
t.Run("time wheel reset", func(t *testing.T) {
tm := NewTimer()
go func() {
tm.Run()
}()
count := int32(0)
tc := make(chan time.Duration, 2)
var mu sync.Mutex
isClose := false
now := time.Now()
node1 := tm.AfterFunc(time.Millisecond*10, func() {
mu.Lock()
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) <= 2 && !isClose {
tc <- time.Since(now)
}
mu.Unlock()
})
node2 := tm.AfterFunc(time.Millisecond*10, func() {
mu.Lock()
atomic.AddInt32(&count, 1)
if atomic.LoadInt32(&count) <= 2 && !isClose {
tc <- time.Since(now)
}
mu.Unlock()
})
node1.Reset(time.Millisecond * 20)
node2.Reset(time.Millisecond * 20)
time.Sleep(time.Millisecond * 40)
mu.Lock()
isClose = true
close(tc)
node1.Stop()
node2.Stop()
mu.Unlock()
for tv := range tc {
if tv < time.Millisecond*20 || tv > 2*time.Millisecond*20 {
t.Errorf("tc < time.Millisecond tc > 2*time.Millisecond")
}
}
if atomic.LoadInt32(&count) != 2 {
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
}
})
}

View File

@@ -0,0 +1,14 @@
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
//
// mit license
package timer
import (
"fmt"
"testing"
)
func Test_Get10Ms(t *testing.T) {
fmt.Printf("%v:%d", get10Ms(), get10Ms())
}