英文:
Issue with goroutine and Waitgroup
问题
我正在尝试迭代一个循环,并在匿名函数上调用go例程,并在每次迭代中添加一个waitgroup。我将一个字符串传递给同一个匿名函数,并将该值附加到切片a上。由于我循环了10000次,所以预期切片的长度应该是10000。但是我看到的是随机数。不确定问题出在哪里。有人可以帮我解决这个问题吗?
这是我的代码片段:
package main
import (
"fmt"
"sync"
)
func main() {
var wg = new(sync.WaitGroup)
var a []string
for i := 0; i <= 10000; i++ {
wg.Add(1)
go func(s string) {
a = append(a, s)
wg.Done()
}("MaxPayne")
}
wg.Wait()
fmt.Println(len(a))
}
英文:
I am trying to iterate a loop and call go routine on an anonymous function and adding a waitgroup on each iteration. And passing a string to same anonymous function and appending the value to slice a. Since I am looping 10000 times length of the slice is expected to be 10000. But I see random numbers. Not sure what is the issue. Can anyone help me fix this problem?
Here is my code snippet
import (
"fmt"
"sync"
)
func main() {
var wg = new(sync.WaitGroup)
var a []string
for i := 0; i <= 10000; i++ {
wg.Add(1)
go func(s string) {
a = append(a, s)
wg.Done()
}("MaxPayne")
}
wg.Wait()
fmt.Println(len(a))
}
答案1
得分: 2
请注意,在追加切片时,实际上是创建了一个新的切片,然后将其重新赋值给切片变量。因此,变量a
存在无控制的并发写入。在Go(以及大多数其他语言)中,并发写入相同的值是不安全的。为了使其安全,您可以使用互斥锁对写入进行串行化。
尝试使用以下代码:
var lock sync.Mutex
var a []string
和
lock.Lock()
a = append(a, s)
lock.Unlock()
以下是一种实现类似结果的模式,但无需使用互斥锁仍然安全。
package main
import (
"fmt"
"sync"
)
func main() {
const sliceSize = 10000
var wg = new(sync.WaitGroup)
var a = make([]string, sliceSize)
for i := 0; i < sliceSize; i++ {
wg.Add(1)
go func(s string, index int) {
a[index] = s
wg.Done()
}("MaxPayne", i)
}
wg.Wait()
}
这个程序与您的其他程序不完全相同,但是它的功能如下:
- 创建一个已经具有所需大小(10000)的切片(每个元素此时都是空字符串)
- 对于每个数字0...9999,创建一个新的goroutine,为其分配一个特定的索引,以便将特定的字符串写入其中
- 在所有goroutine退出并且等待组完成等待之后,我们知道切片的每个索引都已成功填充。
即使没有互斥锁,内存访问现在也是安全的,因为每个goroutine只写入其相应的索引(每个goroutine都有一个唯一的索引)。因此,这些并发内存写入不会相互冲突。在初始创建具有所需大小的切片后,变量a
本身不需要再次赋值,因此原始的内存竞争被消除。
英文:
Notice how appending a slice, you actually make a new slice, and then assign it back to the slice variable. So you have un-controlled concurrent writing to the variable a
. Concurrent writing to the same value is not safe in Go (and most languages). In order to make it safe, you can serialize the writes with a mutex.
Try:
var lock sync.Mutex
var a []string
and
lock.Lock()
a = append(a, s)
lock.Unlock()
For more information about how a mutex works, see the tour and the sync package.
Here is a pattern to achieve a similar result, but without needing a mutex and still being safe.
package main
import (
"fmt"
"sync"
)
func main() {
const sliceSize = 10000
var wg = new(sync.WaitGroup)
var a = make([]string, sliceSize)
for i := 0; i < sliceSize; i++ {
wg.Add(1)
go func(s string, index int) {
a[index] = s
wg.Done()
}("MaxPayne", i)
}
wg.Wait()
}
This isn't exactly the same as your other program, but here's what it does.
- Create a slice that already has the desired size of 10,000 (each element is an empty string at this point)
- For each number 0...9999, create a new goroutine that is given a specific index to write a specific string into
- After all goroutines have exited and the waitgroup is done waiting, then we know that each index of the slice has successfully been filled.
The memory access is now safe even without a mutex, because each goroutine is only writing to it's respective index (and each goroutine gets a unique index). Therefore, none of these concurrent memory writes conflict with each other. After initially creating the slice with the desired size, the variable a
itself doesn't need to be assigned to again, so the original memory race is eliminated.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论