In answering another question我使用 sync.Map
编写了一些结构来缓存API请求。
type PostManager struct {
sync.Map
}
func (pc PostManager) Fetch(id int) Post {
post, ok := pc.Load(id)
if ok {
fmt.Printf("Using cached post %v\n", id)
return post.(Post)
}
fmt.Printf("Fetching post %v\n", id)
post = pc.fetchPost(id)
pc.Store(id, post)
return post.(Post)
}
不幸的是,如果两个goroutine都同时获取同一未缓存的Post,则它们都会发出请求。
var postManager PostManager
wg.Add(3)
var firstPost Post
var secondPost Post
var secondPostAgain Post
go func() {
// Fetches and caches 1
firstPost = postManager.Fetch(1)
defer wg.Done()
}()
go func() {
// Fetches and caches 2
secondPost = postManager.Fetch(2)
defer wg.Done()
}()
go func() {
// Also fetches and caches 2
secondPostAgain = postManager.Fetch(2)
defer wg.Done()
}()
wg.Wait()
我需要确保当同时提取相同的ID时,只允许一个人实际发出请求。另一个必须等待,并将使用缓存的Post。但是也不能锁定不同ID的获取。
在上面的示例中,我希望只有一个调用
pc.fetchPost(1)
和pc.fetchPost(2)
,并且它们应该同时进行。Link to the full code。
最佳答案
golang.org/x/sync/singleflight package正是为此目的而编写的。
请注意,所有缓存访问都应该在传递给Do的回调函数内进行。在链接到注释的代码中,您可以在外部进行查找;这有点违背了目的。
另外,您必须使用指向singleflight.Group的指针。这就是您的数据竞赛的源泉,而 vert 指出了这一点:
./foo.go:41:10: fetchPost passes lock by value: command-line-arguments.PostManager contains golang.org/x/sync/singleflight.Group contains sync.Mutex
这是我的写法(操场上的完整示例:https://play.golang.org/p/2hE721uA88S):
import (
"strconv"
"sync"
"golang.org/x/sync/singleflight"
)
type PostManager struct {
sf *singleflight.Group
cache *sync.Map
}
func (pc *PostManager) Fetch(id int) Post {
x, _, _ := pc.sf.Do(strconv.Itoa(id), func() (interface{}, error) {
post, ok := pc.cache.Load(id)
if !ok {
post = pc.fetchPost(id)
pc.cache.Store(id, post)
}
return post, nil
})
return x.(Post)
}
关于go - 我可以在Go中使用特定值锁定吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60198582/