Go语言的singleflight是一个常用的并发控制工具,它能够避免重复执行相同的操作,并发地执行只需一次的操作。在本文中,我们将对singleflight的源代码进行分析,并介绍一些使用案例。
1. 什么是Singleflight?
Go语言的singleflight是一个常用的并发控制工具,它能够避免重复执行相同的操作,并发地执行只需一次的操作。在本文中,我们将对singleflight的源代码进行分析,并介绍一些使用案例。
什么是Singleflight?
Singleflight是一个Go语言的库,用于避免在高并发环境中执行相同的操作。当多个goroutine需要同时执行同一个操作时,singleflight会保证只有一个goroutine执行该操作,并将结果返回给其他goroutine,从而避免了重复执行相同操作的问题。
单个操作通常是一个昂贵的操作,比如访问数据库或调用远程API,而Singleflight能够帮助我们避免重复执行这样的操作,提高程序的性能和稳定性。
2. Singleflight的源代码分析
Singleflight库的核心代码只有几十行,主要包括了一个Group结构体和一个Do方法,下面是Group结构体的定义:
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
dup map[string]chan<- Result
}
type call struct {
wg sync.WaitGroup
val interface{}
err error
}
type Result struct {
val interface{}
err error
shared bool
}
Group结构体中包含了两个map,一个是m,存储了每个操作的call结构体,另一个是dup,存储了正在执行的操作的channel。当多个goroutine同时执行同一个操作时,只有第一个goroutine会执行操作,其他goroutine会等待第一个goroutine执行完后,直接从m中获取结果。
Do方法是Singleflight库的核心方法,它的定义如下:
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
}
if c, ok := g.m[key]; ok {
g.mu.Unlock()
c.wg.Wait()
return c.val, c.err
}
if ch, ok := g.dup[key]; ok {
g.mu.Unlock()
res := <-ch
if res.shared {
return res.val, res.err
}
return fn()
}
ch := make(chan Result, 1)
g.dup[key] = ch
g.mu.Unlock()
c := new(call)
c.wg.Add(1)
c.val, c.err = fn()
c.wg.Done()
g.mu.Lock()
delete(g.dup, key)
if _, ok := g.m[key]; !ok {
g.m[key] = c
}
g.mu.Unlock()
ch <- Result{c.val, c.err, false}
return c.val, c.err
}
Do方法的流程如下:
- 先获取锁,然后检查操作是否正在执行中(通过m和dup两个map),如果是,则等待操作执行完成并返回结果。
- 如果操作没有在执行中,则在dup中创建一个channel,并将其存储在dup中,然后释放锁。这样其他正在等待执行该操作的goroutine就能从dup中获取该channel,防止重复执行操作。
- 执行操作,并将结果存储在call结构体中。
- 获取锁,并删除dup中的channel。然后再次检查操作是否已经在执行中,如果没有,则将结果存储在m中。
- 释放锁,并将结果发送给其他等待执行该操作的goroutine。
3. 引用案例
- 避免重复调用接口:当多个goroutine需要同时调用同一个接口时,Singleflight能够保证只有一个goroutine执行该操作,从而避免了重复调用接口的问题。
- 避免重复访问数据库:当多个goroutine需要同时访问同一个数据库时,Singleflight能够保证只有一个goroutine执行该操作,从而避免了重复访问数据库的问题。
- 避免重复计算:当多个goroutine需要同时计算同一个值时,Singleflight能够保证只有一个goroutine执行该操作,从而避免了重复计算的问题。
package main
import (
"fmt"
"sync"
"time"
"golang.org/x/sync/singleflight"
)
func main() {
var group singleflight.Group
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
result, err, _ := group.Do("calculation", func() (interface{}, error) {
fmt.Println("Start calculating...")
time.Sleep(time.Second) // 模拟计算耗时
fmt.Println("Calculation finished.")
return 42, nil
})
if err != nil {
fmt.Printf("error: %v\n", err)
return
}
fmt.Printf("result: %v\n", result)
}()
}
wg.Wait()
}
在这个示例中,我们使用Singleflight来避免重复计算一个值。使用group.Do方法计算值,第一次调用时,会进行计算,并将结果存储在group中。其他请求再次调用group.Do方法时,会从group中取出结果,避免了重复计算。
运行上面的程序,可以看到只有第一个goroutine进行了计算,其他goroutine直接返回了结果,避免了重复计算。
Start calculating...
Calculation finished.
result: 42
result: 42
result: 42
result: 42
result: 42
网友评论