Golang中的for循环中i++的增量不一致。

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

Golang for loop with inconsistent adding of i++

问题

我有一个带有goroutines的for循环。我在循环中创建了goroutines,它们打印一个字符串和一个int类型的变量"i"。我知道这些字符串和"i"会以随机顺序打印,而实际上也是这样。但是,正如你下面所看到的,"i"的值没有正确增加。在五个字符串中,有三个或四个字符串的值保持不变,然后跳到2或1。随机顺序中不应该有1、2、3、4、5吗?我做错了什么?

package main

import (
	"fmt"
	"sync"
)

func main() {
	a := []string{
		"apple",
		"orange",
		"grape",
		"peach",
		"lemon",
	}

	wg := sync.WaitGroup{}
	wg.Add(len(a))
	i := 0
	for _, v := range a {
		go func(a string) {
			fmt.Println(a, i)
			i++
			wg.Done()
		}(v)
	}
	wg.Wait()
}

结果1:

orange 0
apple 0
lemon 0
peach 2
grape 0

结果2:

lemon 0
grape 0
peach 0
apple 0
orange 1

我的目标(随机顺序):

lemon 2
grape 4
peach 1
apple 0
orange 3
英文:

I have for loop with goroutines. I create the go routines in the loop which prints a string and "i" which is an int. I know the strings and "i" will print in random order which it does. But "i" is not adding correctly as you can see below. The value stays the same for three or four of the five strings then jumps to 2 or 1. Shouldn't there be a 1, 2, 3, 4, 5 in the random order? What am I doing wrong?

package main

import (
	"fmt"
	"sync"
)

func main() {
	a := []string{
		"apple",
		"orange",
		"grape",
		"peach",
		"lemon",
	}

	wg := sync.WaitGroup{}
	wg.Add(len(a))
	i := 0
	for _, v := range a {
		go func(a string) {
			fmt.Println(a, i)
			i++
			wg.Done()
		}(v)
	}
	wg.Wait()
}

Result 1:

orange 0
apple 0
lemon 0
peach 2
grape 0

Result 2:

lemon 0
grape 0
peach 0
apple 0
orange 1

My Goal (random order)

lemon 2
grape 4
peach 1
apple 0
orange 3

答案1

得分: 8

通过闭包,所有的goroutine共享同一个变量i。尝试使用以下代码:

package main

import (
    "fmt"
    "sync"
)

func main() {
    a := []string{
        "apple",
        "orange",
        "grape",
        "peach",
        "lemon",
    }

    wg := sync.WaitGroup{}
    wg.Add(len(a))
    for i, v := range a {
        go func(a string, j int) {
            fmt.Println(a, j)
            wg.Done()
        }(v, i)
    }
    wg.Wait()
}

总体来说,在你发布的程序中,你在不同的goroutine中读取和写入变量i,而没有进行任何同步。这是一种数据竞争。在这种情况下,任何事情都可能发生。

Go的竞争检测器甚至会警告你:

go run -race test.go
apple 0
==================
WARNING: DATA RACE
Read at 0x00c420010268 by goroutine 7:
  main.main.func1()
      /home/erwo/test.go:22 +0x6d

Previous write at 0x00c420010268 by goroutine 6:
  main.main.func1()
      /home/erwo/test.go:23 +0x191

Goroutine 7 (running) created at:
  main.main()
      /home/erwo/test.go:25 +0x15f

Goroutine 6 (finished) created at:
  main.main()
      /home/erwo/test.go:25 +0x15f
==================
orange 1
==================
WARNING: DATA RACE
Read at 0x00c420010268 by goroutine 8:
  main.main.func1()
      /home/erwo/test.go:22 +0x6d

Previous write at 0x00c420010268 by goroutine 6:
  main.main.func1()
      /home/erwo/test.go:23 +0x191

Goroutine 8 (running) created at:
  main.main()
      /home/erwo/test.go:25 +0x15f

Goroutine 6 (finished) created at:
  main.main()
      /home/erwo/test.go:25 +0x15f
==================
peach 2
grape 2
lemon 4
Found 2 data race(s)
exit status 66

请注意,我只翻译了你提供的代码部分,其他内容不包括在内。

英文:

Through the closures all the goroutines share the same variable i. Try that instead:

package main

import (
    "fmt"
    "sync"
)

func main() {
    a := []string{
        "apple",
        "orange",
        "grape",
        "peach",
        "lemon",
    }

    wg := sync.WaitGroup{}
    wg.Add(len(a))
    for i, v := range a {
        go func(a string, j int) {
            fmt.Println(a, j)
            wg.Done()
        }(v,j)
    }
    wg.Wait()
}

Generally: In the program you posted you are reading i and writing i from different goroutines without any synchronisation. That is a data race. Anything could happen in that scenario.

The go race detector even yells at you

go run -race test.go
apple 0
==================
WARNING: DATA RACE
Read at 0x00c420010268 by goroutine 7:
  main.main.func1()
      /home/erwo/test.go:22 +0x6d

Previous write at 0x00c420010268 by goroutine 6:
  main.main.func1()
      /home/erwo/test.go:23 +0x191

Goroutine 7 (running) created at:
  main.main()
      /home/erwo/test.go:25 +0x15f

Goroutine 6 (finished) created at:
  main.main()
      /home/erwo/test.go:25 +0x15f
==================
orange 1
==================
WARNING: DATA RACE
Read at 0x00c420010268 by goroutine 8:
  main.main.func1()
      /home/erwo/test.go:22 +0x6d

Previous write at 0x00c420010268 by goroutine 6:
  main.main.func1()
      /home/erwo/test.go:23 +0x191

Goroutine 8 (running) created at:
  main.main()
      /home/erwo/test.go:25 +0x15f

Goroutine 6 (finished) created at:
  main.main()
      /home/erwo/test.go:25 +0x15f
==================
peach 2
grape 2
lemon 4
Found 2 data race(s)
exit status 66

答案2

得分: 5

在每个goroutine中,有几件事情正在发生:

  1. 它打印i的值,这个值是在打印语句执行时的值。
  2. 它增加i的值。

不能保证这两件事情是原子性的,也不能确定它们以什么顺序由哪个goroutine执行。

为了得到你想要的结果,你可以:

  1. 使用互斥锁来保护对i的访问(但有时这会削弱并行性的优势)。
  2. i作为参数传递给你的函数(go func(i int){}(i),就像Krom的回答一样)。
  3. 使用原子操作在每个goroutine中将i交换到一个新的局部变量中并递增它(这有点棘手,需要正确处理)。

我建议使用第2种方法。但我也建议不要将goroutine的顺序作为随机性的来源。

英文:

There are several things that are happening in each goroutine:

  1. It prints the value of i as it is at the time the print statement executes.
  2. It increments the value of i.

There is no guarantee that those two things happen atomically, or in what order they will happen by which goroutines.

In order to get your desired result, you can:

  1. Use a mutex to protect access to i (but that kinda defeats the point of paralellism sometimes)
  2. Pass i as an argument to your function (go func(i int){}(i)). (like Krom's answer)
  3. Use atomic operations to swap i to a new local in each goroutine and increment it (a bit tricky to get just right)

I'd recommend 2. But I would also advise against using goroutine order as a source of randomness.

huangapple
  • 本文由 发表于 2017年7月25日 01:22:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/45286540.html
匿名

发表评论

匿名网友

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

确定