2017-12-30 13 views
0

은 내가 이런 식으로 할 수있는 알고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) 
} 

을하지만 우리는 destroyObjectAfterXHours을 상상하면 몇 분 내에 백만 번 호출이 솔루션은 아주 나쁜 것입니다.

나는 누군가가이 문제에 대해보다 효율적인 해결책을 공유 할 수 있기를 바랬다.

나는 파괴 시간과 객체 ID가 어딘가에 저장되어있는 잠재적 인 해결책에 대해 생각 해왔다. 그리고 나서 X 분마다리스트를 가로 지르며 파괴되고 제거되어야 할 객체들을 파괴 할 하나의 func가있을 것이다. 그 정보가 저장된 곳의 ID와 시간 정보. 이것이 좋은 해결책일까요?

나는

+0

유스 케이스를 조금 더 설명 할 수 있습니까? 좀 더 우아한 해결책이 있기 때문에 부탁드립니다. 예를 들어 MongoDB는 타임 스탬프의 TTL 인덱스를 기반으로 문서를 자동으로 삭제합니다. –

답변

2

time.AfterFunc 기능이 사용 사례를 위해 설계되었습니다 :

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

time.AfterFunc 효율적이고 사용하기 간단합니다.

설명서에 설명 된대로 기능은 지속 시간이 경과 한 후에 goroutine에서 호출됩니다. goroutine은 질문 에서처럼 앞쪽에 만들어지지 않습니다.

// 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

내가 이해할 수있는 한, 이것은 자신의 goroutine을 만들 것입니다. 즉, 백만 건의 요청이 여전히 백만 건의 goroutines이 만들어지게 할 것입니다. – fisker

+1

예, 런타임은 타이머가 만료 될 때 함수를 실행하는 goroutine을 만듭니다. 타이머가 만료되는 한, 런타임 구현은 매우 효율적입니다 (런타임은 버킷 힙의 타이머를 유지 관리합니다). –

+0

타이머가 만료되었을 때만? 'destoryObject'가 발생하기 훨씬 전에'/ do do other stuff'가 호출됩니다. 그리고'destroyObject'가 단지 1 밀리 초가 소요된다면, 동시에 백만 그루의 goroutines을 실행하는 것에 대해 걱정할 필요가 없을 것입니다. 그렇다면 사실 너무 좋게 들린다. – fisker

2

그래서 난 '등, 당신이 다음 항목의 수백만의 모든 시간 목록을 통과해야하고 효율적으로 일부 항목을 제거해야하기 때문에 그것은 또한 나쁜 솔루션이 될 것입니다 걱정 백만 번호의 목록을 순회 번호 1

이상의 솔루션 # 2 동의 거라고하는 것보다 훨씬 쉽습니다 만 별도의 이동 루틴

이동 루틴이 (루프에 비해) 비싼 메모리를 가지고 처리 시간. 예를 들면. 백만 개의 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

이것은 흥미로운 질문입니다. 나는 다음 아이템이 파괴 될 때까지 파괴 될 아이템의 큐를 유지하기 위해 힙을 사용하고 정확한 시간 동안 sleep하는 솔루션을 찾는다. 나는 그것이 더 효율적이라고 생각하지만 이득은 어떤 경우에는 가늘다. 그럼에도 불구하고, 당신은 여기에 코드를 볼 수 있습니다

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 밀리 초가 걸립니다 파괴 코드 및 데이터 복사 (100 밀리 초 정도의 규모로 수천 회 이상의 실행에 평균적으로 많은 비용이 소요됩니다).내 코드의 접근 방식은 O (lg N)이며, 10^6 배율은 적어도 1,000 배 더 빠릅니다 (상수를 고려함). 이 모든 계산은 벤치 마크가 아닌 경험에 기반을두고 있음을 다시 한 번 기억하십시오 (제공 할 수는 있지만 제공 할 수는 없습니다).

편집 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입니다 (이유는 궁금합니다). 내 코드, 포인터 + 시간. 시간은 28 바이트입니다. 백만의 규모에서, 그 차이는 희박합니다. After Func를 사용하는 것이 훨씬 더 좋은 방법입니다.

+0

정말 고맙지 만 조금 복잡해 보입니다. P .. 나는 그것을 더 가까이서 연구하려고 노력할 것입니다. 성능이 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 
} 

달성 할 수 취소 할 수있는 파괴 작업 당 단일 골 루틴. 세션이 있고 사용자가 무언가를 했으므로 세션을 활성 상태로 유지하려고합니다. goroutines는 이므로 매우 저렴하므로 다른 goroutine을 간단하게 실행할 수 있습니다.