這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
10.go開源groupcache項目蛤蟆筆記——singleFlight
1 singleFlight
這個包主要實現了一個可合併作業的介面
1.1 先定義一個回呼函數結構call
type call struct {
//類似java的CountdownLatch的
wg sync.WaitGroup
// 回呼函數
val interface{}
// error
err error
}
1.2 定義一個Group結構體
type Group struct {
musync.Mutex // protects m
m map[string]*call // lazily initialized
}
Go使用開頭字母大小寫判斷是否外部可見,大寫外部可見,小寫外部不可見,比如上面的Group在外部實用singleflght包是可以訪問到的,而call不能.
1.3 Do函數
包的主要介面,用於向其他節點發送查詢請求時,合并相同key的請求,減少熱點可能帶來的麻煩,比如說我請求key="123"的資料,在沒有返回的時候又有很多相同key的請求,而此時後面的沒有必要發,只要等待第一次返回的結果即可.
返回是一個inferace和error.入參是一個key和一個函數。
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
// 首先擷取group鎖
g.mu.Lock()
// 映射表不存在就建立1個
if g.m == nil {
g.m = make(map[string]*call)
}
// 判斷要查詢某個key的請求是否已經在處理了
if c, ok := g.m[key]; ok {
// 已經在處理了就釋放group鎖並wait在wg上,我們看到wg加1是在某個時間段內第一次請求時加的
// 並且在完成fn操作返回後會wg done,那時wait等待就會返回,直接返回第一次請求的結果
g.mu.Unlock()
c.wg.Wait()
return c.val, c.err
}
// 如果沒有在處理,則建立回調,wg加1,把回調存到m中表示已經有在請求了,釋放鎖
c := new(call)
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
// 執行fn,釋放wg
c.val, c.err = fn()
c.wg.Done()
// 加鎖將請求從m中刪除,表示請求已經做好了
g.mu.Lock()
delete(g.m, key)
g.mu.Unlock()
return c.val, c.err
}
2 Singleflight_test
2.1 TestDo
定義一個變數g 為Group類型
調用Do函數。傳回值賦值為v.
判斷v是否為bar (string)
2.2 TestDoErr
定義一個變數g 為Group類型
增加一個自訂錯誤如下:
someErr := errors.New("Someerror")
然後調用Do函數。返回為自訂的錯誤。
然後判斷返回的錯誤是否正確。
2.3 TestDoDupSuppress
定義一個變數g 為Group類型
一個返回函數,用與檢測10迴圈也只調用了一次。
fn := func() (interface{}, error) {
atomic.AddInt32(&calls,1)
return<-c, nil
}
定義一個常數10.
迴圈10次
每次wg.Add(1),然後並發go func()
然後等待100ms讓goroutings可以阻塞一會。
函數返回的是一個通道變數。
給通道變數賦值bar.
2.4 測試結果
測試如下
=== RUN TestDo
--- PASS: TestDo (0.00s)
=== RUN TestDoErr
--- PASS: TestDoErr (0.00s)
=== RUN TestDoDupSuppress
--- PASS: TestDoDupSuppress(0.10s)
PASS
ok test 0.227s