问题描述:

如下的函数被并发调用时,如果有重复的URL(在一个routine中被多次访问或多个routine访问同一个url),会产生冗余的网络请求。请实现一个并发安全的缓存,以提高网络的利用效率和降低函数的执行时间。完成后你将熟悉互斥锁的使用。

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)
}

要点:

  • 注意多个routine同时访问同一个URL时只需要发出一个网络请求。

拓展:

代码实现1:

import (
   "sync"
)

type result struct{
   value interface{}
   err error
}

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

type Func func(key string)(interface{}, error)


func New(f Func) *Memo{
   return &Memo{f: f, cache: make(map[string] *entry)}
}

type Memo struct{
   f Func
   mu sync.Mutex
   cache map[string] *entry
}


func (memo *Memo) Get(key string)(value interface{}, err 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
}

代码实现2:

type result struct{
   value interface{}
   err error
}

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

type Func func(key string)(interface{}, error)


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

func New(f Func) *Memo{
   memo := &Memo{requests: make(chan request)}

   go memo.server(f)
   return memo
}

type Memo struct{
   requests chan request
}


func (memo *Memo) Get(key string)(value interface{}, err 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{
         e = &entry{ready: make(chan struct{})}
         cache[req.key] = e
         go e.call(f, req.key)
      }

      go e.deliver(req.response)
   }
}


func (e *entry) call(f Func, key string){
   e.res.value, e.res.err = f(key)
   close(e.ready)
}

func (e *entry) deliver(response chan <- result)  {
   <- e.ready

   response <- e.res
}