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

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

type result struct {
	value interface{}
	err   error
}

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

// request 是一條請求消息,key 需要用Func來調用
type request struct {
	key      string
	response chan<- result // 客戶端端需要單個 result
}

type Memo struct{ requests chan request }

// New 返回f的函數記憶,客戶端之後需要調用 Close
func New(f Func) *Memo {
	memo := &Memo{requests: make(chan request)}
	go memo.server(f)
	return memo
}

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

// 監控goroutine中的cache變量
func (memo *Memo) server(f Func) {
	cache := make(map[string]*entry)
	for req := range memo.requests {
		e := cache[req.key]
		if e == nil {
			// 對這個key的第一次請求
			e = &entry{ready: make(chan struct{})}
			cache[req.key] = e
			// 調用 f(key)
			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
}
package memo

import (
	"testing"

	"main/memo"
	"main/memotest"
)

var httpGetBody = memotest.HTTPGetBody

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

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

 

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