golang:併發非阻塞緩存_共享變量上鎖

《GO程序設計語言》設計中案例,僅作爲筆記進行收藏。併發非阻塞的緩存系統案例,它能解決函數記憶問題,即緩存函數的結果,達到多次調用但只須計算一次的效果。此案例採用共享變量上鎖構建。

package memotest

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sync"
	"testing"
	"time"
)

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

var HTTPGetBody = httpGetBody

func incomingURLs() <-chan string {
	ch := make(chan string)

	go func() {
		for _, url := range []string{
			"https://golang.org",
			"https://godoc.org",
			"https://play.golang.org",
			"http://gopl.io",
			"https://golang.org",
			"https://godoc.org",
			"https://play.golang.org",
			"http://gopl.io",
		} {
			ch <- url
		}
		close(ch)
	}()

	return ch
}

type M interface {
	Get(key string) (interface{}, error)
}

func Sequential(t *testing.T, m M) {
	for url := range incomingURLs() {
		start := time.Now()
		value, err := m.Get(url)
		if err != nil {
			log.Print(err)
			continue
		}
		fmt.Printf("%s, %s, %d bytes\n",
			url, time.Since(start), len(value.([]byte)))
	}
}

func Concurrent(t *testing.T, m M) {
	var n sync.WaitGroup
	for url := range incomingURLs() {
		n.Add(1)
		go func(url string) {
			defer n.Done()
			start := time.Now()
			value, err := m.Get(url)
			if err != nil {
				log.Print(err)
				return
			}
			fmt.Printf("%s, %s, %d bytes\n",
				url, time.Since(start), len(value.([]byte)))
		}(url)
	}
	n.Wait()
}
package memo

import "sync"

// Func 是用於記憶的函數類型
type Func func(string) (interface{}, error)

type result struct {
	value interface{}
	err   error
}

// 解決重複抑制的問題
type entry struct {
	res   result
	ready chan struct{} // res 準備好後會被關閉
}

// Memo 緩存調用 Func 的結果
type Memo struct {
	f     Func
	mu    sync.Mutex // 保護cache
	cache map[string]*entry
}

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

func (memo *Memo) Get(key string) (value interface{}, err error) {
	memo.mu.Lock()
	e := memo.cache[key]
	if e == nil {
		// 對 key 的第一次訪問,這個 goroutine 負責計算數據和廣播數據
		// 已準備完畢的消息
		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 {
		// 對這個 key 的重複訪問
		memo.mu.Unlock()
		// 等待數據準備完畢
		<-e.ready
	}
	return e.res.value, e.res.err
}
package memo

import (
	"testing"

	"main/memo"
	"main/memotest"
)

var httpGetBody = memotest.HTTPGetBody

func Test(t *testing.T) {
	m := memo.New(httpGetBody)
	memotest.Sequential(t, m)
}

func TestConcurrent(t *testing.T) {
	m := memo.New(httpGetBody)
	memotest.Concurrent(t, m)
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章