golang積累-記憶閉包

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

go語言中,作為一等類型的函數,是可以作為值來傳遞和使用。而閉包,則是函數和環境變數的結合。將函數作為參數,利用閉包的特性,可以用簡潔的代碼提供實用的功能。
之前提到call通過wg組合,來規避同一時刻同樣的耗時操作導致系統崩潰。【golang積累-Call回調模式】,這個在Groupcache【github】的代碼中用於同樣資料在惰性載入的時候,對資料庫的過熱請求。具體代碼參見:【singleflight.go】。
但如果不是同一時刻的存取違規,而是one by one一次一次的重複處理,這時我們通常為了避免重複計算,尤其是耗時且又通用的計算處理、資料庫查詢,就會考慮cache。通常會非常親切的在很多地方類似的代碼:

cache:=make(map[string]interface{})//...if _,founded:=cache[key];founded{//do something}else{v:=function(key)cache[key]=v}

如果業務中使用情境較多,可考慮封裝到高階函數中,在函數內部封裝cache進行過濾。

memcache函數的基本形式

//需要被cache結果的函數type memoizeFunction func(int, ...int) int//封裝cache的高階函數,每次運算都會先尋找cache,如果沒有則計算func Memoize(function memoizeFunction)memoizeFunction{    //封裝了的cache    cache:=make(map[string]int)    return func(x int,xs ...int)int{        //1、將函數的輸入參數展開併合並為字串,作為cache的key。對於參數按順序的情況非常實用。        key:=fmt.Sprint(x)        for _,i:=range xs{            key+=fmt.Sprintf(",%d",i)        }        //2、在cache中尋找        if value,found:=cache[key];found{            return value        }        //3、沒有緩衝,則計算,並將結果那入到cache中        value:=function(x,xs...)        cache[key]=value        return value    }}//具體的業務方法非常耗時的計算var caculate = Memoize(func(x int, xs ...int) int {    //通過sleep類比耗時1秒的內部處理    time.Sleep(time.Second)    //隨機返回一個結果    return rand.Intn(10)})func main() {    //類比計算100次,實際只有前10次是真實計算,後邊都會cache出結果。    for i := 0; i < 100; i++ {        caculate(i % 10)    }}

代碼中,Memoize這個函數,其實有類似於result pool的作用。 每次只需要修改caculate內部的具體代碼即可。其是否已被cache還是重新計算都被Memoize進行了封裝。對於這部分代碼,可以理解為:

  1. memoize就是一個獨立的運列區域,
  2. caculate通過Memoize的傳回值定位,訪問只由它可見的cache,可以把第19行代碼轉換為匿名函數,就很清晰:
//...//3、沒有緩衝,則計算,並將結果那入到cache中        value:=func(x,xs...int)int{//<------此處開始,用匿名函數展開caculate的函數代碼        time.Sleep(time.Second)        return rand.Intn(10)        }(x,xs...)        cache[key]=value       //<--------對匿名函數而言,cache是外部公用的        return value

memcache函數的擴充

現實中,前面代碼還有幾個缺陷:
1. 傳回值是整形,限制很大。
2. 輸入值是整形,不適合其他形式。
由於go的文法特徵,對傳回值可以改為interface{},根據具體業務再進行轉換。而輸入值,則根據情況斟酌是否轉換。
在有些資料中,提到斐波拉契函數的計算最佳化,就用到了cache來規避多次遞迴。代碼如下:

package mainimport "fmt"// 將結果形式擴充為interface{}type memoizeFunction func(int, ...int) interface{}var Fibonacci memoizeFunctionfunc init() {    Fibonacci = Memoize(func(x int, xs ...int) interface{} {        if x < 2 {            return x        }        return Fibonacci(x-1).(int) + Fibonacci(x-2).(int)    })}func Memoize(function memoizeFunction) memoizeFunction {    //封裝了的cache    cache := make(map[string]interface{})    return func(x int, xs ...int) interface{} {        key := fmt.Sprint(x)        for _, i := range xs {            key += fmt.Sprintf(",%d", i)        }        if value, found := cache[key]; found {            return value        }        //沒有緩衝,則計算,並將結果那入到cache中        value := function(x, xs...)        cache[key] = value        return value    }}func main() {    fmt.Println("Fibonacci(45)=", Fibonacci(45))}

此時,由於用到了遞迴,感覺會複雜一些。原理其實沒變,

  1. Memoize中的cache是在Fibonacci初始化的時候就已經建立好了,也就是下邊這行代碼出現的時候:

    Fibonacci = Memoize(func(x int, xs …int) interface{} {

  2. Fibonacci遞迴的時候,僅僅就是從key := fmt.Sprint(x)開始執行,這與傳統的遞迴調用是相通的。

其他

代碼中的key轉換,實際上是有序的

key:=fmt.Sprint(x)for _,i:=range xs{    key+=fmt.Sprintf(",%d",i)}

如果入參是無序集合,而集合元素的順序確不同,此時key並不相同,無法直接定位結果,依然會重新計算。考慮將入參改為interface{},並進行排序處理應該可以最佳化。

cache是函數內部私人

如果同一個檔案中,即使多個函數使用Memoize也不用擔心相同key的衝突,因為每個函數都會有一個內部的cache,這是閉包函數的特點。

簡言之,通過封裝cache的記憶閉包,結合call模式,將會大幅提升系統的效能和健壯性。本質上,就是利用文法替代了一些模式上的應用。當然,從代碼風格上,個人認為golang的記憶閉包,比scala的高階函數要難理解。這或許算是個例吧!

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.