亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

如何同步慢速計算并緩存它?

如何同步慢速計算并緩存它?

Go
小怪獸愛吃肉 2022-10-04 19:56:43
在golang后端,我想為多個客戶端提供一個值,讓我們稱之為分數。分數隨時間而變化,計算速度很慢。計算不依賴于先前的結果。當沒有客戶時,我根本不想計算它。因此,計算應該只在有要求的情況下進行。但還有另一個事實 - 分數不能在5秒內改變。所以我嘗試了不同的建議,一切都有其缺點:在客戶缺席的情況下進行昂貴的計算:var score interface{}// run in a separate goroutinefunc calculateScorePeriodically() {    for{        select{        case <-time.After(5*time.Second):            score = calculateScoreExpensiveAndSlow()        }    }}func serveScore(w http.ResponseWriter, r* http.Request) {    b, _ := json.Marshal(score)    w.Write(b)}在很長的計算周期內阻止所有客戶端(但實際上可能只是向它們提供舊數據)。而且你不能移動到互斥體之外,因為這樣多個客戶端可以同時進入計算塊,并且不會在5秒間隔內進行計算,而是按順序進行計算:ifvar (    score interface{}    mutex sync.Mutex    updatedAt time.Time)func getCachedScore() float64 {    mutex.Lock()    defer mutex.Unlock()    currentTime := time.Now()    if currentTime.Sub(updatedAt) < 5*time.Second {        return score    }    updatedAt = currentTime    score = calculateScoreExpensiveAndSlow()    return score}func serveScore(w http.ResponseWriter, r* http.Request) {    b, _ := json.Marshal(getCachedScore())    w.Write(b)}如何解決上述兩個缺點?PS.我認為這是一個通用問題,也是一種模式 - 它有一個特殊的名字嗎?
查看完整描述

2 回答

?
largeQ

TA貢獻2039條經驗 獲得超8個贊

可能有多種解決方案。一個簡單的解決方案是使用指定的 goroutine 進行計算,您可以通過在通道上發送值來表示需要重新計算。發送可能是非阻塞的,因此,如果計算正在進行中,則不會發生任何事情。


下面是一個可重用的緩存實現:


type cache struct {

    mu      sync.RWMutex

    value   interface{}

    updated time.Time


    calcCh     chan struct{}

    expiration time.Duration

}


func NewCache(calc func() interface{}, expiration time.Duration) *cache {

    c := &cache{

        value:   calc(),

        updated: time.Now(),

        calcCh:  make(chan struct{}),

    }


    go func() {

        for range c.calcCh {

            v := calc()


            c.mu.Lock()

            c.value, c.updated = v, time.Now()

            c.mu.Unlock()

        }

    }()


    return c

}


func (c *cache) Get() (value interface{}, updated time.Time) {

    c.mu.RLock()

    value, updated = c.value, c.updated

    c.mu.RUnlock()


    if time.Since(updated) > c.expiration {

        // Trigger a new calculation (will happen in another goroutine).

        // Do non-blocking send, if a calculation is in progress,

        // this will have no effect

        select {

        case c.calcCh <- struct{}{}:

        default:

        }

    }


    return

}


func (c *cache) Stop() {

    close(c.calcCh)

}

注意:是停止背景戈魯廷。呼叫后,不得調用。Cache.Stop()Cache.Stop()Cache.Get()


將其用于您的情況:


func getCachedScore() interface{} {

    // ...

}


var scoreCache = NewCache(getCachedScore, 5*time.Second)


func serveScore(w http.ResponseWriter, r* http.Request) {

    score, _ := scoreCache.Get()

    b, _ := json.Marshal(score)

    w.Write(b)

}


查看完整回答
反對 回復 2022-10-04
?
汪汪一只貓

TA貢獻1898條經驗 獲得超8個贊

這是我實現的,與icza的答案相關,但具有更多功能:


package common


import (

    "context"

    "sync/atomic"

    "time"

)


type (

    CachedUpdater func() interface{}

    ChanStruct    chan struct{}

)


type Cached struct {

    value        atomic.Value  // holds the cached value's interface{}

    updatedAt    atomic.Value  // holds time.Time, time when last update sequence was started at

    updatePeriod time.Duration // controls minimal anount of time between updates

    needUpdate   ChanStruct

}


//cachedUpdater is a user-provided function with long expensive calculation, that gets current state

func MakeCached(ctx context.Context, updatePeriod time.Duration, cachedUpdater CachedUpdater) *Cached {

    v := &Cached{

        updatePeriod: updatePeriod,

        needUpdate:   make(ChanStruct),

    }

    //v.updatedAt.Store(time.Time{}) // "was never updated", but time should never be nil interface

    v.doUpdate(time.Now(), cachedUpdater)

    go v.updaterController(ctx, cachedUpdater)

    return v

}


//client will get cached value immediately, and optionally may trigger an update, if value is outdated

func (v *Cached) Get() interface{} {

    if v.IsExpired(time.Now()) {

        v.RequestUpdate()

    }

    return v.value.Load()

}


//updateController goroutine can be terminated both by cancelling context, provided to maker, or by closing chan

func (v *Cached) Stop() {

    close(v.needUpdate)

}


//returns true if value is outdated and updater function was likely not called yet

func (v *Cached) IsExpired(currentTime time.Time) bool {

    updatedAt := v.updatedAt.Load().(time.Time)

    return currentTime.Sub(updatedAt) > v.updatePeriod

}


//requests updaterController to perform update, using non-blocking send to unbuffered chan. controller can decide not to update in case if it has recently updated value

func (v *Cached) RequestUpdate() bool {

    select {

    case v.needUpdate <- struct{}{}:

        return true

    default:

        return false

    }

}


func (v *Cached) updaterController(ctx context.Context, cachedUpdater CachedUpdater) {

    for {

        select {

        case <-ctx.Done():

            return

        case _, ok := <-v.needUpdate:

            if !ok {

                return

            }

            currentTime := time.Now()

            if !v.IsExpired(currentTime) {

                continue

            }

            v.doUpdate(currentTime, cachedUpdater)

        }

    }

}


func (v *Cached) doUpdate(currentTime time.Time, cachedUpdater CachedUpdater) {

    v.updatedAt.Store(currentTime)

    v.value.Store(cachedUpdater())

}


查看完整回答
反對 回復 2022-10-04
  • 2 回答
  • 0 關注
  • 84 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號