如何解决这个数据竞争问题?

huangapple go评论121阅读模式
英文:

How can I get rid of this data race

问题

我有这两个函数:

// PartyHub 结构体包含了聚会的所有数据
type PartyHub struct {
    FullPartys    map[string]Party
    PartialPartys map[string]Party
    Enter         chan Member
    Leave         chan Member
    sync.Mutex
}


// RemoveFromQueue 函数将会从聚会中移除成员
func (p *PartyHub) RemoveFromQueue(memberLeaving Member, inQueue bool) {
    if !inQueue {
        return
    }
    for _, party := range p.PartialPartys {
        go func(party Party) {
            if _, ok := party.Members[memberLeaving.Identifier]; ok {
                p.Lock()
                delete(party.Members, memberLeaving.Identifier)
                p.Unlock()
            }
        }(party)
    }
    log.Println("Removing")
}

// SortIntoParty 函数将会将成员分配到聚会中
func (p *PartyHub) SortIntoParty(newMember Member, inQueue bool) {
    log.Println(inQueue)
    if inQueue {
        return
    }
    log.Println("Adding")
    foundParty := false
    for partyid, party := range p.PartialPartys {
        if !party.Accepting {
            continue
        }

        goodFitForParty := true
        for _, partyMember := range party.Members {
            if newMember.Type == partyMember.Type && newMember.Rank >= partyMember.Rank-partyMember.RankTol && newMember.Rank <= partyMember.Rank+partyMember.RankTol {
                goodFitForParty = true
                continue
            } else {
                goodFitForParty = false
                break
            }
        }

        if !goodFitForParty {
            continue
        } else {
            foundParty = true
            newMember.Conn.CurrentParty = partyid
            p.Lock()
            p.PartialPartys[partyid].Members[newMember.Conn.Identifier] = newMember
            p.Unlock()
            if len(party.Members) == 2 {
                p.Lock()
                party.Accepting = false
                p.Unlock()
                // Start Go Routine
            }
            break
        }
    }
    if !foundParty {
        uuid := feeds.NewUUID().String()
        newMember.Conn.CurrentParty = uuid
        p.Lock()
        p.PartialPartys[uuid] = Party{Accepting: true, Members: make(map[string]Member), Ready: make(chan *Connection), Decline: make(chan *Connection)}
        p.PartialPartys[uuid].Members[newMember.Conn.Identifier] = newMember
        p.Unlock()
    }
}

我在代码中使用了 -&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; 来标记这两段代码的访问位置,我不确定如何在不发生数据竞争的情况下保持这两段代码的最新状态,我对Go语言还比较新,想知道如何在读写这个变量时避免数据竞争。

英文:

I have these 2 functions:

// PartyHub struct contains all data for the party
type PartyHub struct {
	FullPartys    map[string]Party
	PartialPartys map[string]Party
	Enter         chan Member
	Leave         chan Member
	sync.Mutex
}


// RemoveFromQueue will remove the member from party
func (p *PartyHub) RemoveFromQueue(memberLeaving Member, inQueue bool) {
	if !inQueue {
		return
	}
	for _, party := range p.PartialPartys {
		go func(party Party) {
			if _, ok := party.Members[memberLeaving.Identifier]; ok {
				p.Lock()
-&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;		delete(party.Members, memberLeaving.Identifier)
				p.Unlock()
			}
		}(party)
	}
	log.Println(&quot;Removing&quot;)
}

// SortIntoParty will sort the member into party
func (p *PartyHub) SortIntoParty(newMember Member, inQueue bool) {
    log.Println(inQueue)
	if inQueue {
		return
	}
	log.Println(&quot;Adding&quot;)
	foundParty := false
-&gt;&gt; for partyid, party := range p.PartialPartys {
		if !party.Accepting {
			continue
		}

		goodFitForParty := true
		for _, partyMember := range party.Members {
			if newMember.Type == partyMember.Type &amp;&amp; newMember.Rank &gt;= partyMember.Rank-partyMember.RankTol &amp;&amp; newMember.Rank &lt;= partyMember.Rank+partyMember.RankTol {
				goodFitForParty = true
				continue
			} else {
				goodFitForParty = false
				break
			}
		}

		if !goodFitForParty {
			continue
		} else {
			foundParty = true
			newMember.Conn.CurrentParty = partyid
			p.Lock()
			p.PartialPartys[partyid].Members[newMember.Conn.Identifier] = newMember
			p.Unlock()
			if len(party.Members) == 2 {
				p.Lock()
				party.Accepting = false
				p.Unlock()
				// Start Go Routine
			}
			break
		}
	}
	if !foundParty {
		uuid := feeds.NewUUID().String()
		newMember.Conn.CurrentParty = uuid
		p.Lock()
		p.PartialPartys[uuid] = Party{Accepting: true, Members: make(map[string]Member), Ready: make(chan *Connection), Decline: make(chan *Connection)}
		p.PartialPartys[uuid].Members[newMember.Conn.Identifier] = newMember
		p.Unlock()
	}
}

I put -&gt;&gt;&gt;&gt;&gt;&gt; next to where the 2 pieces of code are being accessed, I'm not sure how I can keep these 2 up to date without being in a data race, fairly new to go and wondering how I should be reading and writing this variable without a data-race.

答案1

得分: 1

你的问题中有很多代码,但看起来你正在尝试在一个goroutine中删除map(party.Members)的元素,同时在另一个goroutine中循环遍历它。这听起来像是一个难以维护、容易出错的灾难,但是可以通过使用互斥锁来避免内存竞争。

你需要一个互斥锁来保护对map的访问(包括读和写),而难点在于确保在for/range循环期间保持锁定。下面是一种方法,在for循环开始之前持有锁,并在循环体内解锁。

var mut sync.Mutex
var m = map[string]int{}

func f(key string) {
    mut.Lock()
    defer mut.Unlock()
    delete(m, key)
}

func g() {
    mut.Lock()
    defer mut.Unlock()
    for k, v := range m {
        mut.Unlock()
        fmt.Println(k, v)
        mut.Lock()
    }
}

在这个例子中,可以同时调用任意数量的fg,而不会发生内存竞争。

更简单易懂的方法是在循环内部不解锁/加锁互斥锁,这意味着在f中的删除操作将等待g中正在运行的循环完成(或反之亦然)。

英文:

You've got a lot of code in your question, but it looks like you're trying to delete elements from a map (party.Members) in one goroutine, while looping over it in another. This sounds like an unmaintainable, error-ridden disaster in the making, but it's possible to do without memory races.

You need a mutex to protect access (both read and write) to the map, and the hard part is to make sure the lock is held during the for/range iteration. Here's one way to do it, by having the lock held before the for loop starts, and unlocking it inside the body of the loop.

var mut sync.Mutex
var m = map[string]int{}

func f(key string) {
    mut.Lock()
    defer mut.Unlock()
    delete(m, key)
}

func g() {
    mut.Lock()
    defer mut.Unlock()
    for k, v := range m {
        mut.Unlock()
        fmt.Println(k, v)
        mut.Lock()
    }
}

Here, any combination of fs and gs can be called concurrently without memory races.

Drastically simpler to understand would be to not Unlock/Lock the mutex inside the loop, which would mean a deletion in f would wait for any running loop in g to complete (or vice-versa).

huangapple
  • 本文由 发表于 2015年12月26日 14:28:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/34469080.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定