An overlooked Go Package: Single Flight
The singleflight
package in Go provides a mechanism to suppress duplicate function calls, ensuring that only one execution is in-flight for a given key at a time. This is particularly useful in scenarios where multiple goroutines might simultaneously request the same resource, such as fetching data from an API or a database.
Why Use singleflight
?
Consider an application that fetches data from an API or a database. If several requests trigger the same call, this might result in multiple identical database or API calls. These redundant requests can strain your system, leading to wasted CPU cycles, memory, and bandwidth. By using singleflight
, we can eliminate these duplicate calls efficiently.
Example Usage
Here’s an example demonstrating how to use singleflight
:
package main
import (
"fmt"
"sync"
"time"
"golang.org/x/sync/singleflight"
)
var (
group singleflight.Group
cache = make(map[string]string)
mu sync.Mutex
)
func fetchData(key string) (string, error) {
// Simulate a slow operation
time.Sleep(2 * time.Second)
return "data for " + key, nil
}
func getData(key string) (string, error) {
mu.Lock()
if data, found := cache[key]; found {
mu.Unlock()
return data, nil
}
mu.Unlock()
v, err, _ := group.Do(key, func() (interface{}, error) {
data, err := fetchData(key)
if err != nil {
return nil, err
}
mu.Lock()
cache[key] = data
mu.Unlock()
return data, nil
})
if err != nil {
return "", err
}
return v.(string), nil
}
func main() {
keys := []string{"key1", "key1", "key2", "key1", "key2"}
var wg sync.WaitGroup
for _, key := range keys {
wg.Add(1)
go func(k string) {
defer wg.Done()
data, err := getData(k)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Fetched:", data)
}(key)
}
wg.Wait()
}
In this example, even though we have multiple goroutines simultaneously requesting data for the same key, the fetchData
function will only be called once per unique key due to the group.Do
function from singleflight
.
You may also see singleflight
used in serverless scenarios. For instance, Google App Engine uses it as part of its init
function since it does not have a main.go
.