singleflight provides a duplicate function call suppression mechanism. you create a key to identify the request. when there’re other requests with the same key, it will wait for the answer which is processing for another request. |
|
package main |
|
import ( "errors" "fmt" "log" "strconv" "sync" "time" |
|
"golang.org/x/sync/singleflight" ) |
|
var ( g singleflight.Group ErrCacheMiss = errors.New("cache miss") ) |
|
simulating 10 concurrent request |
func main() { var wg sync.WaitGroup fmt.Println("===default query===") wg.Add(10) |
for i := 0; i < 10; i++ { go func() { defer wg.Done() data, err := loadDefault("key") if err != nil { log.Print(err) return } log.Println(data) }() } wg.Wait() |
|
simulating 10 concurrent request |
fmt.Println("") fmt.Println("===wrap query with singleflight===") wg.Add(10) |
for i := 0; i < 10; i++ { go func() { defer wg.Done() data, err := loadWithSingleflight("key") if err != nil { log.Print(err) return } log.Println(data) }() } wg.Wait() } |
|
func loadDefault(key string) (string, error) { data, err := loadFromCache(key) if err != nil && err == ErrCacheMiss { data, err := loadFromDB(key) if err != nil { log.Println(err) return "", err } setCache(key, data) } return data, nil } |
|
load from cache, if missed, load from db and set cache |
func loadWithSingleflight(key string) (string, error) { data, err := loadFromCache(key) if err != nil && err == ErrCacheMiss { v, err, _ := g.Do(key, func() (interface{}, error) { data, err := loadFromDB(key) if err != nil { return nil, err } setCache(key, data) return data, nil }) if err != nil { log.Println(err) return "", err } data = v.(string) } return data, nil } |
simulate cache miss |
func loadFromCache(key string) (string, error) { return "", ErrCacheMiss } |
setCache |
func setCache(key, data string) {} |
getDataFromDB |
func loadFromDB(key string) (string, error) { fmt.Println("query db") unix := strconv.Itoa(int(time.Now().UnixNano())) return unix, nil } |
default query: every query goes to db |
$go run sync-singleflight.go
|
===default query=== query db query db query db query db query db query db query db query db query db query db 2023/01/24 12:24:19 2023/01/24 12:24:19 2023/01/24 12:24:19 2023/01/24 12:24:19 2023/01/24 12:24:19 2023/01/24 12:24:19 2023/01/24 12:24:19 2023/01/24 12:24:19 2023/01/24 12:24:19 2023/01/24 12:24:19 |
|
only one query goes to db. others goroutine will use that result. |
===wrap query with singleflight=== query db 2023/01/24 12:24:19 1674534259333265000 2023/01/24 12:24:19 1674534259333265000 2023/01/24 12:24:19 1674534259333265000 2023/01/24 12:24:19 1674534259333265000 2023/01/24 12:24:19 1674534259333265000 2023/01/24 12:24:19 1674534259333265000 2023/01/24 12:24:19 1674534259333265000 2023/01/24 12:24:19 1674534259333265000 2023/01/24 12:24:19 1674534259333265000 2023/01/24 12:24:19 1674534259333265000 |
Next example: Pattern: Singleton.