为什么这个循环被认为是数据竞争?[Golang]

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

How come this loop is considered a data race [Golang]

问题

我有一个循环,并且出现了这个错误,我正在运行 go run -race main.go

这是有问题的循环:

var wg sync.WaitGroup

for _, member := range p.Members { << 第254行
	go func() {
		wg.Add(1)
		_, err := db.Exec("UPDATE party_members SET active = ? WHERE steamid = ?", false, member.SteamID) << 第257行
		if err != nil {
			log.Println(err)
		}
		_, err = db.Exec("INSERT INTO party_members SET belongs_to =?, active = ?, steamid = ?", partyUUID, true, member.SteamID)
		if err != nil {
			log.Println(err)
		}
		wg.Done()
	}() << 第266行
}

然后这是我得到的错误:

警告:数据竞争
由goroutine 44读取:
  BLAH/controllers/partybot.func·001()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:257 +0x136

之前由goroutine 43写入:
  BLAH/controllers/partybot.(*PartialParty).SendReadyCheck()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:254 +0xda5

Goroutine 44(正在运行)创建于:
  BLAH/controllers/partybot.(*PartialParty).SendReadyCheck()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:266 +0xf13

Goroutine 43(已完成)创建于:
  BLAH/controllers/partybot.(*PartyHub).SortMemberIntoParty()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:173 +0xdb9
  BLAH/controllers/partybot.(*Connection).readPump()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:359 +0x1872
  BLAH/controllers/partybot.WSPartyBot()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:440 +0x4f0
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1265 +0x4e
  github.com/gorilla/mux.(*Router).ServeHTTP()
      /Users/Dev/gocode/src/github.com/gorilla/mux/mux.go:98 +0x377
  BLAH/controllers/common.func·001()
      /Users/Dev/gocode/src/BLAH/controllers/common/common.go:36 +0xc2
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1265 +0x4e
  net/http.(*ServeMux).ServeHTTP()
      /usr/local/go/src/net/http/server.go:1541 +0x20c
  net/http.serverHandler.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1703 +0x1f6
  net/http.(*conn).serve()
      /usr/local/go/src/net/http/server.go:1204 +0x1087
==================

我不确定为什么会出现数据竞争,因为我对Go还比较新,如果有人能告诉我为什么会出现这个问题,那就太好了,我在问题中标记了相关的行。

英文:

I have a loop and I get this error, I'm running go run -race main.go

This is the loop in question:

var wg sync.WaitGroup

for _, member := range p.Members { << Line 254
	go func() {
		wg.Add(1)
		_, err := db.Exec("UPDATE party_members SET active = ? WHERE steamid = ?", false, member.SteamID) << Line 257
		if err != nil {
			log.Println(err)
		}
		_, err = db.Exec("INSERT INTO party_members SET belongs_to =?, active = ?, steamid = ?", partyUUID, true, member.SteamID)
		if err != nil {
			log.Println(err)
		}
		wg.Done()
	}() << Line 266
}

and then this is the error I get :

WARNING: DATA RACE
Read by goroutine 44:
  BLAH/controllers/partybot.func·001()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:257 +0x136

Previous write by goroutine 43:
  BLAH/controllers/partybot.(*PartialParty).SendReadyCheck()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:254 +0xda5

Goroutine 44 (running) created at:
  BLAH/controllers/partybot.(*PartialParty).SendReadyCheck()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:266 +0xf13

Goroutine 43 (finished) created at:
  BLAH/controllers/partybot.(*PartyHub).SortMemberIntoParty()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:173 +0xdb9
  BLAH/controllers/partybot.(*Connection).readPump()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:359 +0x1872
  BLAH/controllers/partybot.WSPartyBot()
      /Users/Dev/gocode/src/BLAH/controllers/partybot/partybot.go:440 +0x4f0
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1265 +0x4e
  github.com/gorilla/mux.(*Router).ServeHTTP()
      /Users/Dev/gocode/src/github.com/gorilla/mux/mux.go:98 +0x377
  BLAH/controllers/common.func·001()
      /Users/Dev/gocode/src/BLAH/controllers/common/common.go:36 +0xc2
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1265 +0x4e
  net/http.(*ServeMux).ServeHTTP()
      /usr/local/go/src/net/http/server.go:1541 +0x20c
  net/http.serverHandler.ServeHTTP()
      /usr/local/go/src/net/http/server.go:1703 +0x1f6
  net/http.(*conn).serve()
      /usr/local/go/src/net/http/server.go:1204 +0x1087
==================

I'm not sure why this is a Data race as I'm fairly new to Go anyways if anybody could tell me why this is that would be great, I marked the lines in question.

答案1

得分: 10

问题在于goroutine通过闭包访问变量'member',在Go语言中,这个引用在到达时被评估(即它看到的是goroutine执行时的member值,而不是通过go func()...调用创建时的值)。member变量正在被循环更新,所以循环更新和你启动的goroutine读取它们之间存在竞争(如果你进一步深入,你会发现你的数据库没有看到集合中的确切成员集合)。通常,你可以通过在循环中强制对变量进行评估来解决这个问题,可以通过创建一个局部变量或将其作为参数添加到函数中来实现,例如:

var wg sync.WaitGroup

for _, member := range p.Members {
    wg.Add(1)
    m := member
    go func() {
        _, err := db.Exec("UPDATE party_members SET active = ? WHERE steamid = ?", false, m.SteamID)
        ...
        wg.Done()
    }()
}

或者

var wg sync.WaitGroup

for _, member := range p.Members {
    wg.Add(1)
    go func(m memberType) {
        _, err := db.Exec("UPDATE party_members SET active = ? WHERE steamid = ?", false, m.SteamID)
        ...
        wg.Done()
    }(member)
}

还要注意,你需要从循环中调用wg.Add(1),而不是在goroutine内部调用。

英文:

the problem is that the goroutine is accessing the variable 'member' via the closure, and in go this reference is evaluated when its reached (i.e. it sees the value of member as of when the goroutine executes, not when it was created via the go func()... call. the member variable is being updated by the loop, so there's a race between the loop updates and the goroutines you've started reading them. (you'll find if you dig further that your db doesn't see the exact set of members from the collection). Typically you resolve this by forcing an evaluation of the variable in the loop, either by creating a local var, or by adding it as a parameter to the func, e.g.

var wg sync.WaitGroup

for _, member := range p.Members {
    wg.Add(1)
    m := member
    go func() {
        _, err := db.Exec("UPDATE party_members SET active = ? WHERE steamid = ?", false, m.SteamID)
        ...
        wg.Done()
    }() 
}

or

var wg sync.WaitGroup

for _, member := range p.Members {
    wg.Add(1)
    go func(m memberType) {
        _, err := db.Exec("UPDATE party_members SET active = ? WHERE steamid = ?", false, m.SteamID)
        ...
        wg.Done()
    }(member) 
}

also note that you need to call wg.Add(1) from the loop, not from within the goroutine itself.

huangapple
  • 本文由 发表于 2015年12月2日 12:42:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/34035072.html
匿名

发表评论

匿名网友

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

确定