2017-12-30 61 views
0

私は、このように行うことができます知っている:私たちはdestroyObjectAfterXHoursを想像した場合X時間後にfuncを効率的に呼び出す方法は?

func randomFunc() { 
    // do stuff 
    go destroyObjectAfterXHours(4, "idofobject") 
    // do other stuff 
} 

func destroyObjectAfterXHours(hours int, id string) { 
    time.Sleep(hours * time.Hour) 
    destroyObject(id) 
} 

しかしが数分以内に百万回呼び出され、このソリューションは非常に悪いとなります。

私は誰かがこの問題に対してより効率的な解決法を共有できることを望んでいました。

私は、破壊時間とオブジェクトIDがどこかに格納されている可能性のある解決策について考えていました。そして、X分ごとにリストをたどる1つの関数があります。その情報が格納されていた場所からのIDと時刻の情報。これは良い解決策でしょうか?

私はだから私は」

+0

あなたのユースケースについてもう少し説明できますか?より洗練されたソリューションがあるかもしれないので私は尋ねています。たとえば、MongoDBでは、タイムスタンプ上のTTLインデックスに基づいてドキュメントを自動削除できます。 –

答えて

2

time.AfterFunc機能は、このユースケースのために設計されています

func randomFunc() { 
    // do stuff 
    time.AfterFunc(4*time.Hour, func() { destroyObject("idofobject") }) 
    // do other stuff 
} 

time.AfterFuncは、効率的で使用するのは簡単です。

ドキュメントに記載されているように、期間が経過すると、関数はゴルーチンで呼び出されます。ゴルーチンは質問のように前に作成されていません。

// Same as above, though since id may have already been destroyed 
// once, I name the channel different 
done := make(chan bool) 

go func(t time.Duration,id int){ 

    // Sends to the channel every t 
    tick := time.NewTicker(t).C 

    // Wrap, otherwise select will only execute the first tick 
    for{ 
    select { 
     // t has passed, so id can be destroyed 
     case <-tick: 
     destroyObject(id) 
     // We are finished destroying stuff 
     case <-done: 
     fmt.Println("Ok, ok, I quit destroying...") 
     return 
    } 
    } 
}() 

if weather == weather.RAINY { 
    done <- true 
} 

その背後にある考え方は、実行することです:それはワンショットがある場合は、それを4時間毎に行いたい場合は

+0

私が理解できる限り、これは独自のゴルーチンを生み出します。つまり、百万の要求が依然として百万のゴルーチンを作り出すことになります。 – fisker

+1

はい、ランタイムは、タイマーの期限が切れたときに関数を実行するためのゴルーチンを作成します。タイマーの期限切れに関する限り、ランタイムの実装は非常に効率的です(ランタイムは、バケットのタイマーヒープを維持します)。 –

+0

タイマーが期限切れになったときのみ?意味 '' //他のものは '' destoryObject'が起こるずっと前に呼び出されますか?そして、 'destroyObject'が1ミリ秒かかるだけなら、同時に100万のゴルーチンを動かすことを心配する必要はありません。そのような場合は、本当にうまくいくとは思われません。 – fisker

2

など、それはまた、あなたがしてアイテムの数百万人にリストをトラバースするすべての時間を持つことになりますので、悪いソリューションであること、そして効率的にアイテムの一部を削除しなければならない心配百万番号のリストを横断

は万の別々のゴールーチン

ゴールーチンは、(ループと比較して)高価であり、メモリを取ることよりもはるかに簡単である数1よりあなたの溶液#2に同意Dと、処理時間。たとえば。百万のGoルーチンは約4GBのRAMを使います。

一方、リストを移動するにはスペースがほとんどなく、O(n)の時間に実行されます。

:この正確な機能の

良い例が行くのルーチンでの期限切れの要素を削除ゴーキャッシュされ、これは、彼らがそれをやった方法のより詳細な例である定期

https://github.com/patrickmn/go-cache/blob/master/cache.go#L931

を実行します

type Item struct { 
    Object  interface{} 
    Expiration int64 
} 


func (item Item) Expired() bool { 
    if item.Expiration == 0 { 
     return false 
    } 
    return time.Now().UnixNano() > item.Expiration 
} 

func RemoveItem(s []Item, index int) []int { 
    return append(s[:index], s[index+1:]...) 
} 

func deleteExpired(items []Item){ 
    var deletedItems []int 
    for k, v := range items { 
     if v.Expired(){ 
      deletedItems = append(deletedItems, k) 
     } 
    } 
    for key, deletedIndex := range deleteditems{ 
     items = RemoveItem(items, deletedIndex) 
    } 
} 

上記の実装は、配列ではなくリンクリストではっきりと改善される可能性がありますが、これは一般的な考えです。

+0

本当にありがとう、ありがとうございます。<3 – fisker

1

これは興味深い質問です。私は、次のアイテムが破棄されるまで、破棄するアイテムのキューを維持するためにヒープを使用し、まさに時間の間スリープする解決策になります。私はそれがより効率的だと思うが、利得はスリムである場合もある。それにもかかわらず、あなたはここにコードを見ることができます:

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の経験に基づいて、約10^9のループを約1.2秒で処理することができます。つまり、10^6のスケールでは、リスト全体をトラバースするには約1ミリ秒以上の時間がかかります破壊コードとデータコピー(何千回もの実行で平均して100ミリ秒程度のコストがかかります)。私のコードのアプローチはO(lg N)で、10^6スケールで少なくとも1000倍高速です(定数を考慮して)。これらの計算はすべてベンチマークではなく経験に基づいていることに注意してください(ただし、私はそれらを提供することはできません)。

編集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] 
} 

を、それはもはやunefficientlyデータをコピーしないと、余分なスペースを割り当てません。

編集3: 変更コードhere 私はafterfuncのメモリ使用をテストしました。私のラップトップでは通話あたり250バイトですが、palygroundでは69です(理由は不思議です)。私のコードでは、ポインタ+ time.Timeは28バイトです。 100万の規模では、その差は小さい。 After Funcを使用する方がはるかに良い選択肢です。

+0

本当に感謝していますが、私にとってはちょっと複雑すぎるようです。私はそれをもっと近くで勉強しようとします。パフォーマンスはcjdsの回答よりも良いと思いますか? – fisker

+0

ありがとうございます<3私は、Ceriseの 'time.AfterFunc'が私の望むやり方で動作しない場合にコードを統合しようとします(: – fisker

+0

ヒープは確かに問題の良い解決策ですが、ランタイムのヒープのタイマー? –

0

、これは簡単に

// Make the destruction cancelable 
cancel := make(chan bool) 

go func(t time.Duration, id int){ 
expired := time.NewTimer(t).C 
select { 
    // destroy the object when the timer is expired 
    case <-expired: 
    destroyObject(id) 
    // or cancel the destruction in case we get a cancel signal 
    // before it is destroyed 
    case <-cancel: 
    fmt.Println("Cancelled destruction of",id) 
    return 
} 
}(time.Hours * 4, id) 

if weather == weather.SUNNY { 
    cancel <- true 
} 

で達成することができますキャンセル可能な破壊ジョブごとに1つのゴルーチン。セッションがあり、ユーザーは何かをしたので、セッションを生き生きとしておきたいとします。 goroutinesは極端になので、別のゴルーチンを起動することができます。

関連する問題