我可以在 Go 中使用特定值进行锁定吗?
Can I lock using specific values in Go?
In answering another question I wrote a little struct using 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)
}
不幸的是,如果两个 goroutines 同时获取相同的未缓存 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)
,并且它们应该是同时的。
您可以使用两张地图来做到这一点,一张保存缓存的值,另一张保存正在获取的值。您还需要将锁保留更长的时间,这样就不需要保留 sync.Map,一个普通的地图就可以了。这样的东西应该可以工作(未经测试):
type PostManager struct {
sync.Mutex
cached map[int]Post
loading map[int]chan struct{}
}
您需要处理以下加载失败的情况:
// Need to pass pointer pc
func (pc *PostManager) Fetch(id int) Post {
pc.Lock()
post, ok:=pc.cached[id]
if ok {
pc.Unlock()
return post
}
// See if it is being loaded
loading, ok:=pc.loading[id]
if ok {
// Wait for the loading to complete
pc.Unlock()
<-loading
// Reload
pc.Lock()
post,ok:=pc.cached[id]
// Maybe you need to handle the case where loading failed?
pc.Unlock()
return post
}
// load it
loading=make(chan struct{})
pc.loading[id]=loading
pc.Unlock()
post = pc.fetchPost(id)
pc.Lock()
pc.cached[id]=post
delete(pc.loading,id)
pc.Unlock()
close(loading)
return post
}
如果抓取已经在进行中,似乎可以使用第二张地图等待。
type PostManager struct {
sync.Map
q 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)
if c, loaded := pc.q.LoadOrStore(id, make(chan struct{})); !loaded {
post = pc.fetchPost(id)
pc.Store(id, post)
close(c.(chan struct{}))
} else {
<-c.(chan struct{})
post,_ = pc.Load(id)
}
return post.(Post)
}
或者,更复杂一点,使用相同的地图;-)
func (pc *PostManager) Fetch(id int) Post {
p, ok := pc.Load(id)
if !ok {
fmt.Printf("Fetching post %v\n", id)
if p, ok = pc.LoadOrStore(id, make(chan struct{})); !ok {
fetched = pc.fetchPost(id)
pc.Store(id, fetched)
close(p.(chan struct{}))
return fetched
}
}
if cached, ok := p.(Post); ok {
fmt.Printf("Using cached post %v\n", id)
return cached
}
fmt.Printf("Wating for cached post %v\n", id)
<-p.(chan struct{})
return pc.Fetch(id)
}
golang.org/x/sync/singleflight package正是为此而写的。
请注意,所有缓存访问都应该在传递给 Do 的回调函数内发生。在代码中,您 link 在您的评论中进行了外部查找;这有点违背了目的。
此外,您必须使用指向 singleflight.Group 的指针。那是您的数据竞赛的来源,请兽医指出:
./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)
}
In answering another question I wrote a little struct using 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)
}
不幸的是,如果两个 goroutines 同时获取相同的未缓存 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)
,并且它们应该是同时的。
您可以使用两张地图来做到这一点,一张保存缓存的值,另一张保存正在获取的值。您还需要将锁保留更长的时间,这样就不需要保留 sync.Map,一个普通的地图就可以了。这样的东西应该可以工作(未经测试):
type PostManager struct {
sync.Mutex
cached map[int]Post
loading map[int]chan struct{}
}
您需要处理以下加载失败的情况:
// Need to pass pointer pc
func (pc *PostManager) Fetch(id int) Post {
pc.Lock()
post, ok:=pc.cached[id]
if ok {
pc.Unlock()
return post
}
// See if it is being loaded
loading, ok:=pc.loading[id]
if ok {
// Wait for the loading to complete
pc.Unlock()
<-loading
// Reload
pc.Lock()
post,ok:=pc.cached[id]
// Maybe you need to handle the case where loading failed?
pc.Unlock()
return post
}
// load it
loading=make(chan struct{})
pc.loading[id]=loading
pc.Unlock()
post = pc.fetchPost(id)
pc.Lock()
pc.cached[id]=post
delete(pc.loading,id)
pc.Unlock()
close(loading)
return post
}
如果抓取已经在进行中,似乎可以使用第二张地图等待。
type PostManager struct {
sync.Map
q 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)
if c, loaded := pc.q.LoadOrStore(id, make(chan struct{})); !loaded {
post = pc.fetchPost(id)
pc.Store(id, post)
close(c.(chan struct{}))
} else {
<-c.(chan struct{})
post,_ = pc.Load(id)
}
return post.(Post)
}
或者,更复杂一点,使用相同的地图;-)
func (pc *PostManager) Fetch(id int) Post {
p, ok := pc.Load(id)
if !ok {
fmt.Printf("Fetching post %v\n", id)
if p, ok = pc.LoadOrStore(id, make(chan struct{})); !ok {
fetched = pc.fetchPost(id)
pc.Store(id, fetched)
close(p.(chan struct{}))
return fetched
}
}
if cached, ok := p.(Post); ok {
fmt.Printf("Using cached post %v\n", id)
return cached
}
fmt.Printf("Wating for cached post %v\n", id)
<-p.(chan struct{})
return pc.Fetch(id)
}
golang.org/x/sync/singleflight package正是为此而写的。
请注意,所有缓存访问都应该在传递给 Do 的回调函数内发生。在代码中,您 link 在您的评论中进行了外部查找;这有点违背了目的。
此外,您必须使用指向 singleflight.Group 的指针。那是您的数据竞赛的来源,请兽医指出:
./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)
}