3.3 SingleFlight
在并发编程中,有时我们希望避免多次对同一资源的重复请求,从而节省系统资源和提高效率。Go 语言中的 singleflight 包提供了一种解决方案,能够在多 goroutine 并发请求同一资源时进行合并,确保相同请求只执行一次。下面我们详细介绍 SingleFlight 的概念、使用方法及示例。
3.3.1 什么是SingleFlight
SingleFlight 是 Go 语言 golang.org/x/sync/singleflight 包中的一种同步机制,用于合并并发的相同请求,确保相同的请求在同一时间只执行一次。SingleFlight 的核心思想是,如果有多个 goroutine 同时请求同一个资源,SingleFlight 会合并这些请求,只执行一次实际的请求操作,并将结果返回给所有请求的 goroutine。
SingleFlight 主要有以下特点:
- 请求合并:将并发的相同请求合并为一个,避免重复计算和资源浪费。
- 结果共享:将实际请求的结果共享给所有发起相同请求的 goroutine。
- 错误处理:支持错误传播,如果实际请求出错,所有请求方都能收到相同的错误信息。
3.3.2 SingleFlight的使用方法
使用 SingleFlight 非常简单。首先,创建一个 Group 实例,然后通过 Do 方法发起请求,SingleFlight 会处理请求的合并和结果共享。以下是基本使用示例:
package main
import (
"fmt"
"golang.org/x/sync/singleflight"
"sync"
"time"
)
func expensiveOperation(key string) (string, error) {
time.Sleep(2 * time.Second)
return fmt.Sprintf("Result for %s", key), nil
}
func main() {
var g singleflight.Group
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
v, err, shared := g.Do("myKey", func() (interface{}, error) {
return expensiveOperation("myKey")
})
if err != nil {
fmt.Printf("Goroutine %d error: %v\n", i, err)
return
}
fmt.Printf("Goroutine %d got result: %s (shared: %t)\n", i, v.(string), shared)
}(i)
}
wg.Wait()
}
在这个例子中,多个 goroutine 同时请求 expensiveOperation,由于使用了 SingleFlight,它们的请求会被合并,只执行一次实际的请求操作,结果会共享给所有发起请求的 goroutine。
3.3.3 SingleFlight的应用场景
SingleFlight 适用于以下场景:
- 缓存失效:在缓存失效时,避免多个请求同时访问数据库或外部 API。
- 去重请求:避免重复请求同一资源,例如防止短时间内多次点击按钮导致的重复请求。
- 资源密集型操作:对于资源密集型操作(如复杂计算、网络请求),使用 SingleFlight 可以减少系统负载。
3.3.4 示例代码
以下是一个使用 SingleFlight 实现缓存失效处理的示例:
package main
import (
"fmt"
"golang.org/x/sync/singleflight"
"sync"
"time"
)
type Cache struct {
mu sync.Mutex
data map[string]string
group singleflight.Group
}
func NewCache() *Cache {
return &Cache{
data: make(map[string]string),
}
}
func (c *Cache) Get(key string) (string, error) {
c.mu.Lock()
value, ok := c.data[key]
c.mu.Unlock()
if ok {
return value, nil
}
v, err, _ := c.group.Do(key, func() (interface{}, error) {
return expensiveOperation(key)
})
if err != nil {
return "", err
}
c.mu.Lock()
c.data[key] = v.(string)
c.mu.Unlock()
return v.(string), nil
}
func expensiveOperation(key string) (string, error) {
time.Sleep(2 * time.Second)
return fmt.Sprintf("Result for %s", key), nil
}
func main() {
cache := NewCache()
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
value, err := cache.Get("myKey")
if err != nil {
fmt.Printf("Goroutine %d error: %v\n", i, err)
return
}
fmt.Printf("Goroutine %d got value: %s\n", i, value)
}(i)
}
wg.Wait()
}
在这个示例中,我们定义了一个 Cache 结构体,使用 SingleFlight 处理缓存失效时的并发请求,避免多次执行 expensiveOperation。
结论
SingleFlight 提供了一种优雅的方式来合并并发的相同请求,避免重复计算和资源浪费。在实际应用中,通过使用 SingleFlight,可以有效提高系统的性能和资源利用率。在接下来的章节中,我们将继续探讨其他同步原语和并发编程技巧,帮助您更好地掌握 Go 的并发编程。