这是一个有趣的问题。我提出了一个解决方案,它使用堆来维护要销毁的项目队列并准确休眠,直到下一个项目被销毁。我认为它更有效,但在某些情况下收益可能很小。尽管如此,您可以在此处查看代码:
package main
import (
"container/heap"
"fmt"
"time"
)
type Item struct {
Expiration time.Time
Object interface{} // It would make more sence to be *interface{}, but not as convinient
}
//MINIT is the minimal interval for delete to run. In most cases, it is better to be set as 0
const MININT = 1 * time.Second
func deleteExpired(addCh chan Item) (quitCh chan bool) {
quitCh = make(chan bool)
go func() {
h := make(ExpHeap, 0)
var t *time.Timer
item := <-addCh
heap.Push(&h, &item)
t = time.NewTimer(time.Until(h[0].Expiration))
for {
//Check unfinished incoming first
for incoming := true; incoming; {
select {
case item := <-addCh:
heap.Push(&h, &item)
default:
incoming = false
}
}
if delta := time.Until(h[0].Expiration); delta >= MININT {
t.Reset(delta)
} else {
t.Reset(MININT)
}
select {
case <-quitCh:
return
//New Item incoming, break the timer
case item := <-addCh:
heap.Push(&h, &item)
if item.Expiration.After(h[0].Expiration) {
continue
}
if delta := time.Until(item.Expiration); delta >= MININT {
t.Reset(delta)
} else {
t.Reset(MININT)
}
//Wait until next item to be deleted
case <-t.C:
for !h[0].Expiration.After(time.Now()) {
item := heap.Pop(&h).(*Item)
destroy(item.Object)
}
if delta := time.Until(h[0].Expiration); delta >= MININT {
t.Reset(delta)
} else {
t.Reset(MININT)
}
}
}
}()
return quitCh
}
type ExpHeap []*Item
func (h ExpHeap) Len() int {
return len(h)
}
func (h ExpHeap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h ExpHeap) Less(i, j int) bool {
return h[i].Expiration.Before(h[j].Expiration)
}
func (h *ExpHeap) Push(x interface{}) {
item := x.(*Item)
*h = append(*h, item)
}
func (h *ExpHeap) Pop() interface{} {
old, n := *h, len(*h)
item := old[n-1]
*h = old[:n-1]
return item
}
//Auctural destroy code.
func destroy(x interface{}) {
fmt.Printf("%v @ %v\n", x, time.Now())
}
func main() {
addCh := make(chan Item)
quitCh := deleteExpired(addCh)
for i := 30; i > 0; i-- {
t := time.Now().Add(time.Duration(i) * time.Second / 2)
addCh <- Item{t, t}
}
time.Sleep(7 * time.Second)
quitCh <- true
}
游乐场:https://play.golang.org/p/JNV_6VJ_yfK
顺便说一句,有像cron这样的包用于工作管理,但我不熟悉它们,所以我不能说它们的效率。
编辑:
仍然我没有足够的声誉来发表评论:(
关于性能:此代码基本上具有较少的 CPU 使用率,因为它仅在必要时自行唤醒,并且仅遍历要销毁的项目而不是整个列表。根据个人(实际是ACM经验),大概现代CPU可以在1.2秒左右处理一个10^9的循环,也就是说在10^6的规模上,遍历整个列表大约需要1毫秒以上,不包括实际销毁代码AND 数据复制(在 100 毫秒左右的范围内,运行数千次,平均成本会很高)。我的代码的方法是 O(lg N),它在 10^6 的规模上至少快一千倍(考虑到常数)。请再次注意,所有这些计算都是基于经验而不是基准(有但我无法提供)。
编辑 2:
再想一想,我认为简单的解决方案可以使用简单的优化:
func deleteExpired(items []Item){
tail = len(items)
for index, v := range items { //better naming
if v.Expired(){
tail--
items[tail],items[index] = v,items[tail]
}
}
deleteditems := items[tail:]
items:=items[:tail]
}
通过此更改,它不再低效地复制数据并且不会分配额外的空间。
编辑 3:
从here更改代码
我测试了afterfunc的memoryuse。在我的笔记本电脑上,每次调用是 250 字节,而在 palyground 上是 69(我很好奇原因)。使用我的代码,指针 + 时间。时间是 28 字节。在百万的规模上,差异很小。使用 After Func 是一个更好的选择。