《The.Go.Programming.Language.2015.11.pdf》之函數緩衝實現__函數

來源:互聯網
上載者:User

串列的實現 利用go並存執行 添加互斥鎖 最終方法1使用指標標記 最終方法2使用用戶端伺服器模型

文中9.7節 Example:Concurrent Non-Blocking Cache
該例子實現一個功能,對函數進行緩衝,這樣函數對同樣的參數只需要計算一次。該方法還是concurrent-safe的,並且避免了對整個緩衝加鎖引起的競爭。

我們先來看串列的實現 串列的實現

func httpGetBody(url string) (interface{}, error) {    resp, err := http.Get(url)    if err != nil {        return nil, err    }    defer resp.Body.Close()    return ioutil.ReadAll(resp.Body)}type result struct {    value interface{}    err   error}type Func func(key string) (interface{}, error)type Memo struct {    f     Func    cache map[string]result}func New(f Func) *Memo {    return &Memo{f: f, cache: make(map[string]result)}}func (memo *Memo) Get(key string) (interface{}, error) {    res, ok := memo.cache[key]    if !ok {        res.value, res.err = memo.f(key)        memo.cache[key] = res    }    return res.value, res.err}func testCache() {    incomingURLS := []string{"http://cn.bing.com/", "http://www.baidu.com", "http://cn.bing.com/", "http://www.baidu.com",        "http://www.baidu.com", "http://cn.bing.com/", "http://www.baidu.com",        "http://www.baidu.com", "http://cn.bing.com/", "http://www.baidu.com",        "http://www.baidu.com", "http://cn.bing.com/", "http://www.baidu.com",        "http://www.baidu.com", "http://cn.bing.com/", "http://www.baidu.com"}    m := New(httpGetBody)    allstart := time.Now()    for _, url := range incomingURLS {        start := time.Now()        value, err := m.Get(url)        if err != nil {            fmt.Println(err)        }        fmt.Printf("%s, %s, %d bytes\n",            url, time.Since(start), len(value.([]byte)))    }    fmt.Printf("all %s\n", time.Since(allstart))}

執行結果

http://cn.bing.com/, 180.576553ms, 120050 byteshttp://www.baidu.com, 25.863523ms, 99882 byteshttp://cn.bing.com/, 397ns, 120050 byteshttp://www.baidu.com, 245ns, 99882 byteshttp://www.baidu.com, 154ns, 99882 byteshttp://cn.bing.com/, 123ns, 120050 byteshttp://www.baidu.com, 136ns, 99882 byteshttp://www.baidu.com, 123ns, 99882 byteshttp://cn.bing.com/, 127ns, 120050 byteshttp://www.baidu.com, 188ns, 99882 byteshttp://www.baidu.com, 116ns, 99882 byteshttp://cn.bing.com/, 123ns, 120050 byteshttp://www.baidu.com, 118ns, 99882 byteshttp://www.baidu.com, 180ns, 99882 byteshttp://cn.bing.com/, 140ns, 120050 byteshttp://www.baidu.com, 124ns, 99882 bytesall 206.583298ms
利用go並存執行

我們利用sync.WaitGroup來等待所有URL解析完成

func testCache() {    incomingURLS := []string{"http://cn.bing.com/", "http://www.baidu.com", "http://cn.bing.com/", "http://www.baidu.com",        "http://www.baidu.com", "http://cn.bing.com/", "http://www.baidu.com",        "http://www.baidu.com", "http://cn.bing.com/", "http://www.baidu.com",        "http://www.baidu.com", "http://cn.bing.com/", "http://www.baidu.com",        "http://www.baidu.com", "http://cn.bing.com/", "http://www.baidu.com"}    m := New(httpGetBody)    allstart := time.Now()    var n sync.WaitGroup    for _, url := range incomingURLS {        start := time.Now()        n.Add(1)        go func(url string) {            value, err := m.Get(url)            if err != nil {                fmt.Println(err)            }            fmt.Printf("%s, %s, %d bytes\n",                url, time.Since(start), len(value.([]byte)))            n.Done()        }(url)        n.Wait()    }    fmt.Printf("all %s\n", time.Since(allstart))}

結果可以看到時間更短了,但是裡面出現競爭關係了。

    if !ok {        res.value, res.err = memo.f(key)        memo.cache[key] = res    }

可能一個goroutine判斷!ok時,另外的goroutine也判斷!ok,f還是執行了多次。 添加互斥鎖

type Memo struct {    f     Func    mu    sync.Mutex    cache map[string]result}func (memo *Memo) Get(key string) (interface{}, error) {    memo.mu.Lock()    defer memo.mu.Unlock()    res, ok := memo.cache[key]    if !ok {        res.value, res.err = memo.f(key)        memo.cache[key] = res    }    return res.value, res.err}

但是這樣造成一個問題,把Memo重新變回了串列訪問。 最終方法1:使用指標標記

作者的思路就是實現這樣的一個結果,一個goroutine調用函數,完成耗時的工作,其他調用同樣函數的goroutine等待函數執行完畢後立馬擷取結果。

type result struct {    value interface{}    err   error}type entry struct {    res   result    ready chan struct{}}type Func func(key string) (interface{}, error)type Memo struct {    f     Func    mu    sync.Mutex    cache map[string]*entry}func New(f Func) *Memo {    return &Memo{f: f, cache: make(map[string]*entry)}}func (memo *Memo) Get(key string) (interface{}, error) {    memo.mu.Lock()    e := memo.cache[key]    if e == nil {        e = &entry{ready: make(chan struct{})}        memo.cache[key] = e        memo.mu.Unlock()        e.res.value, e.res.err = memo.f(key)        close(e.ready)    } else {        memo.mu.Unlock()        <-e.ready    }    return e.res.value, e.res.err}

Memo的cache成員由map[string]result變為map[string]*entry
entry的結構為:

type entry struct {    res   result    ready chan struct{}}

ready通道用來通知其他goroutine函數執行完畢可以讀取結果了。
代碼的核心在Get函數中的下面部分

    memo.mu.Lock()    e := memo.cache[key]    if e == nil {        e = &entry{ready: make(chan struct{})}        memo.cache[key] = e        memo.mu.Unlock()

將函數f的計算從鎖地區中分離開了,通過memo.cache[key] = e實現只有一個goroutine會執行函數運算。 最終方法2:使用用戶端伺服器模型

專門一個服務進程負責緩衝,其它goroutine向該服務進程請求函數結果。

    // Func is the type of the function to memoize.    type Func func(key string) (interface{}, error)    // A result is the result of calling a Func.    type result struct {        value interface{}        err        error    }    type entry struct {        res        result        ready chan struct{} // closed when res is ready    }

下面是關鍵區段代碼

type request struct {    key      string    response chan<- result}type Memo struct {    requests chan request}

Memo的成員是一個實值型別為request的通道requests,用來向伺服器發送函數請求,request類型包含一個result的通道,傳遞給伺服器後,伺服器用來給相應的goroutine傳遞函數的結果。
New函數主要建立了request通道和啟動伺服器:

func New(f Func) *Memo {    memo := &Memo{request: make(chan request)}    go memo.server(f)    return memo}

Get函數建立result通道response,構建resquest,然後通過requests通道發送給伺服器,通過response接受函數結果。

func (memo *Memo) Get(key string) (interface{}, error) {    response := make(chan result)    memo.requests <- request{key, response}    res := <-response    return res.value, res.err}func (memo *Memo) Close() {    close(memo.requests)}

接下來是伺服器程式

func (memo *Memo) server(f Func) {    cache := make(map[string]*entry)    for req := range memo.requests {        e := cache[req.key]        if e == nil {            // This is the first request for this key.            e = &entry{ready: make(chan struct{})}            cache[req.key] = e            go e.call(f, req.key) // call f(key)        }        go e.deliver(req.response)    }}func (e *entry) call(f Func, key string) {    // Evaluate the function.    e.res.value, e.res.err = f(key)    // Broadcast the ready condition.    close(e.ready)}func (e *entry) deliver(response chan<- result) {    //等待函數執行結束    <-e.ready    //發送結果到用戶端    response <- e.res}
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.