为了账号安全,请及时绑定邮箱和手机立即绑定

我可以在 Go 中使用特定值锁定吗?

我可以在 Go 中使用特定值锁定吗?

Go
红糖糍粑 2022-05-18 10:08:01
在回答另一个问题时,我编写了一个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 PostManagerwg.Add(3)var firstPost Postvar secondPost Postvar secondPostAgain Postgo 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)andpc.fetchPost(2)并且它们应该是同时的。
查看完整描述

3 回答

?
杨魅力

TA贡献1811条经验 获得超6个赞

golang.org/x/sync/singleflight包正是为此目的而编写的。


请注意,所有缓存访问都应该发生在传递给 Do 的回调函数中。在您在评论中链接到的代码中,您在外部进行查找;这有点违背了目的。


此外,您必须使用指向 singleflight.Group 的指针。这就是你的数据竞赛的来源,请去 vet 指出:


./foo.go:41:10:fetchPost 按值传递锁:命令行参数。PostManager 包含 golang.org/x/sync/singleflight.Group 包含 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)

}


查看完整回答
反对 回复 2022-05-18
?
牛魔王的故事

TA贡献1830条经验 获得超3个赞

如果提取已经在进行中,看起来可以使用第二张地图等待。


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)

}


查看完整回答
反对 回复 2022-05-18
?
素胚勾勒不出你

TA贡献1827条经验 获得超9个赞

您可以使用两张地图来做到这一点,一张保留缓存的值,另一张保留正在获取的值。您还需要将锁保持更长时间,这样就不需要保持同步地图,常规地图就可以了。像这样的东西应该可以工作(未经测试):


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

}


查看完整回答
反对 回复 2022-05-18
  • 3 回答
  • 0 关注
  • 106 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信