- 两种方式实现:
- 使用一个互斥量来保护被多个调用Get的goroutine访问的map变量。当多个goroutine存在并发请求同一个key时,只会执行一次,其他的并发请求会等待,当请求完成,广播通知
- 使用一个goroutine【server】限制变量,串行修改变量cache,从而可以不使用锁
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"sync"
"time"
)
func main() {
testNew1()
testNew2()
}
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 Func func(key string) (interface{}, error)
var incomeUrls = []string{
"https://www.baidu.com",
"https://www.jd.com",
"https://www.taobao.com",
"https://www.taobao.com",
"https://www.bilibili.com",
"https://www.baidu.com",
"https://www.baidu.com",
}
func testNew1() {
m := New(httpGetBody)
var wg sync.WaitGroup
for _, url := range incomeUrls {
wg.Add(1)
go func(url string) {
defer wg.Done()
start := time.Now()
value, err := m.Get(url)
if err != nil {
log.Fatal(err)
}
fmt.Printf("new1:%s %s %d bytes\n", url, time.Since(start), len(value.([]byte)))
}(url)
}
wg.Wait()
}
type result struct {
value interface{}
err error
ready chan struct{}
}
type Memo struct {
mu sync.Mutex
f Func
cache map[string]*result
}
func New(f Func) *Memo {
return &Memo{f: f, cache: make(map[string]*result)}
}
func (m *Memo) Get(key string) (interface{}, error) {
m.mu.Lock()
res := m.cache[key]
if res == nil {
res = &result{ready: make(chan struct{})}
m.cache[key] = res
m.mu.Unlock()
res.value, res.err = m.f(key)
close(res.ready)
} else {
m.mu.Unlock()
<-res.ready
}
return res.value, res.err
}
func testNew2() {
m := New2(httpGetBody)
defer m.Close()
var wg sync.WaitGroup
for _, url := range incomeUrls {
wg.Add(1)
go func(url string) {
defer wg.Done()
start := time.Now()
value, err := m.Get(url)
if err != nil {
log.Fatal(err)
}
fmt.Printf("new2:%s %s %d bytes\n", url, time.Since(start), len(value.([]byte)))
}(url)
}
wg.Wait()
}
type result2 struct {
value interface{}
err error
ready chan struct{}
}
type request struct {
key string
response chan<- *result2
}
type Memo2 struct {
requests chan request
}
func New2(f Func) *Memo2 {
m := &Memo2{requests: make(chan request)}
go m.server(f)
return m
}
func (m *Memo2) Get(key string) (interface{}, error) {
response := make(chan *result2)
m.requests <- request{key: key, response: response}
res := <-response
return res.value, res.err
}
func (m *Memo2) Close() {
close(m.requests)
}
func (m *Memo2) server(f Func) {
cache := make(map[string]*result2)
for req := range m.requests {
e := cache[req.key]
if e == nil {
e = &result2{ready: make(chan struct{})}
cache[req.key] = e
go func(res *result2, key string) {
res.value, res.err = f(key)
close(res.ready)
}(e, req.key)
}
go func(res *result2, response chan<- *result2) {
<-res.ready
response <- res
}(e, req.response)
}
}
摘自《Go程序设计语言》P216