Golang 如何保证数据在一个 goroutine 中完成,同时被另一个 goroutine 访问

标签 go

大家好,我正在尝试使用 websockets 制作一个聚会系统,人们可以在其中进入队列,然后与 5 个与他们相似的人匹配。现在我在这部分遇到了问题:

type PartyHub struct {
    Partys             map[string]*Party
    PartialPartys      []*PartialParty
    Queue              []*Member
    AddParty           chan *Party
    RemoveParty        chan *Party
    AddPartialParty    chan *PartialParty
    RemovePartialParty chan *PartialParty
    EnterQueue         chan *Member
    LeaveQueue         chan *Member
    Mu                 sync.Mutex
}
// Run will begin monitoring the channels
// to register and unregister partys as they are
// created or destroyed
func (p *PartyHub) Run() {
    for {
        select {
        case member := <-p.EnterQueue:
            go p.SortMemberIntoParty(member)
            go p.SortMemberIntoParty(member)
            go p.SortMemberIntoParty(member)
            go p.SortMemberIntoParty(member)
            go p.SortMemberIntoParty(member)

            log.Println(p.PartialPartys)
        case party := <-p.AddPartialParty:
            p.Mu.Lock()
            defer p.Mu.Unlock()
            p.PartialPartys = append(p.PartialPartys, party)
        }
    }
}

// SortMemberIntoParty will take a new user entering the queue and find an appropriate Party
// for the member to join, taking into account RankTollerance, Rank
func (p *PartyHub) SortMemberIntoParty(member *Member) {
    p.Mu.Lock()
    defer p.Mu.Unlock()
    if len(p.PartialPartys) == 0 {
        log.Println("Here")
        newParty := &PartialParty{Accepting: true, Members: []*Member{member}}
        p.AddPartialParty <- newParty
        return
    }

    foundPartyForMember := false
    for _, party := range p.PartialPartys {
        goodFitForParty := true
        for _, partyMember := range party.Members {
            log.Println(member.Type == partyMember.Type, member.Rank >= partyMember.Rank-partyMember.RankTol, member.Rank <= partyMember.Rank+partyMember.RankTol)
            if member.Type == partyMember.Type && member.Rank >= partyMember.Rank-partyMember.RankTol && member.Rank <= partyMember.Rank+partyMember.RankTol {

                goodFitForParty = true
                continue
            } else {
                goodFitForParty = false
                break
            }
        }

        if !goodFitForParty {
            continue
        } else {
            foundPartyForMember = true
            party.Mu.Lock()
            defer party.Mu.Unlock()
            party.Members = append(party.Members, member)
            if len(party.Members) == 5 {
                party.Accepting = false
                go party.SendReadyCheck()
            }
            break
        }
    }


    if !foundPartyForMember {
        newParty := &PartialParty{Accepting: true, Members: []*Member{member}}
        p.AddPartialParty <- newParty
    }

    log.Println("Sorting Members")
}

唯一的问题是,5 个 goroutines 似乎比数据知道发生了什么更快完成。

例如:p.PartialPartys 表示它没有派对。

我需要的是让 p.PartialPartys 始终为每个访问 PartyHub 结构字段的 goroutine 保持最新sync.Mutex 会为我做这件事,但似乎并非如此,有人能告诉我让我的所有 goroutine 与相同数据保持同步的最佳方法吗?

最佳答案

因此,通过此实现,您的五个 goroutine 都无法并行运行,因为它们都在尝试获取 p.Mu 互斥量。看看您使用 p.AddPartialParty channel 的方式,如果代码会死锁,我不会感到惊讶。

考虑以下事件序列:

  1. 其中一个 SortMemberIntoParty goroutine 开始运行并获取互斥量。
  2. 它在 p.AddPartialParty 上发送一个值,该值由 Run 接收。 Run 然后尝试获取互斥锁,因此阻塞。
  3. 原始 SortMemberIntoParty goroutine 完成并释放互斥体。
  4. 不同的 SortMemberIntoParty goroutine 获取互斥量,并尝试将另一个值发送到 p.AddPartialParty
  5. goroutine 阻塞是因为没有人准备好读取值(Run 在返回到 select 语句之前仍在等待互斥量)。<

现在您已经有了一个阻塞的 goroutine,它持有 channel 接收端所需的锁。另请注意,在 (4) 处您不会看到新的 PartialParty,因为 Run 尚未设法添加它。

如果您确实需要互斥量,那么直接让您的 SortMemberIntoParty goroutine 更新 p.PartialPartys 比使用 channel 更容易:您已经知道没有其他人会同时访问该变量。

同样值得记住的是,这个互斥锁本质上意味着所有 SortMemberIntoParty goroutines 都将被序列化。如果您使用 goroutines 是希望在这里实现并行性,那么互斥量会打败它。

关于Golang 如何保证数据在一个 goroutine 中完成,同时被另一个 goroutine 访问,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34011131/

相关文章:

go - 在 Go 中,如何关闭长时间运行的读取?

go - 相互并发的 Go 例程中的死锁错误

go - 使用 `encoding/json` 的默认值 golang 结构?

google-app-engine - Google App Engine 使用哪些 Golang 包

linux - 错误消息 "go: go.mod file not found in current directory or any parent directory; see ' go help modules'"

jquery - 如何在 Jquery 和 golang 中使用输入的 json 数据进行 ajax 调用?

戈朗 : "go get github.com/.." errors with "cannot find header file" - where is this configured?

go - 如何取消引用动态多级指针?

go - 无法将功能添加到接口(interface)

go - Go 的 Syscall() 中的第二个 `r2` 返回值是做什么用的?