循环内的互斥锁导致意外的输出。

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

Mutex within loop leads to unexpected output

问题

我有这段简单的代码(或者在这里https://play.golang.org/p/KW8_OHUp9v)

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. func main() {
  7. mutex := new(sync.Mutex)
  8. for i := 1; i < 5; i++ {
  9. for j := 1; j < 5; j++ {
  10. mutex.Lock()
  11. go func() {
  12. fmt.Printf("%d + %d = %d\n", i, j, j+i)
  13. mutex.Unlock()
  14. }()
  15. }
  16. }
  17. }

它产生以下输出:

  1. 1 + 2 = 3
  2. 1 + 3 = 4
  3. 1 + 4 = 5
  4. 2 + 5 = 7
  5. 2 + 2 = 4
  6. 2 + 3 = 5
  7. 2 + 4 = 6
  8. 3 + 5 = 8
  9. 3 + 2 = 5
  10. 3 + 3 = 6
  11. 3 + 4 = 7
  12. 4 + 5 = 9
  13. 4 + 2 = 6
  14. 4 + 3 = 7
  15. 4 + 4 = 8
  16. 程序已退出。

看到输出结果,我对以下几点感到惊讶:

  1. j 没有出现 '1'
  2. j 出现了 '5'
  3. i=1 只有3个值,而不是4个

我可以理解为什么没有 '1',因为变量在写入之前已经递增。

有人可以解释一下第2点和第3点吗?

英文:

I have this simplistic piece of code (or here https://play.golang.org/p/KW8_OHUp9v)

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;sync&quot;
  5. )
  6. func main() {
  7. mutex := new(sync.Mutex)
  8. for i := 1; i &lt; 5; i++ {
  9. for j := 1; j &lt; 5; j++ {
  10. mutex.Lock()
  11. go func() {
  12. fmt.Printf(&quot;%d + %d = %d\n&quot;, i, j, j+i)
  13. mutex.Unlock()
  14. }()
  15. }
  16. }
  17. }

It produces an output like this

  1. 1 + 2 = 3
  2. 1 + 3 = 4
  3. 1 + 4 = 5
  4. 2 + 5 = 7
  5. 2 + 2 = 4
  6. 2 + 3 = 5
  7. 2 + 4 = 6
  8. 3 + 5 = 8
  9. 3 + 2 = 5
  10. 3 + 3 = 6
  11. 3 + 4 = 7
  12. 4 + 5 = 9
  13. 4 + 2 = 6
  14. 4 + 3 = 7
  15. 4 + 4 = 8
  16. Program exited.

Looking at the output I was surprised by few things:

  1. There are no '1's for the j
  2. There are '5's for the j
  3. There are only 3 values for i=1, instead of 4

I can understand lack of '1's as the variable is incremented before it is written.

Can someone explain 2. and 3. ?

答案1

得分: 3

你正在在循环中闭包变量,并在单独的线程中运行该闭包,而这些变量仍然在改变。当你这样做时,要预料到意想不到的结果 - 例如,你看到5是因为j在最后一次迭代中递增到5,导致循环结束,但j仍然保持5,这样单独的线程就可以读取它。这与你的互斥锁无关,而是与跨线程共享变量有关。如果你这样使用:

  1. go func(i,j int) {
  2. fmt.Printf("%d + %d = %d\n", i, j, j+i)
  3. mutex.Unlock()
  4. }(i,j)

那么它将在启动你的goroutine时传入ij的值,并且后续的迭代不会影响它:https://play.golang.org/p/P3kUP5e1Fp

英文:

You're closing over variables in a loop and then running the closure in a separate thread, while those variables continue to change. When you do this, expect the unexpected - for example, you see 5s because j is incremented to 5 on the last iteration, which causes the loop to end, but j still holds 5, which the separate thread can then read. It has nothing to do with your mutex; it's the cross-thread sharing of variables. If you use:

  1. go func(i,j int) {
  2. fmt.Printf(&quot;%d + %d = %d\n&quot;, i, j, j+i)
  3. mutex.Unlock()
  4. }(i,j)

Then it will pass in the values of i and j at the time your goroutine is started, and subsequent iterations won't affect it: https://play.golang.org/p/P3kUP5e1Fp

答案2

得分: 1

当你执行这段代码时:

  1. go func() {
  2. fmt.Printf("%d + %d = %d\n", i, j, j+i)
  3. mutex.Unlock()
  4. }()

当前的 goroutine 创建了另一个循环来增加 j 的值。

增加操作在 printf 之前进行,这就是为什么即使在调用函数时 j 小于 5,它也可能在函数来得及打印出值之前增加到 5 的原因。

换句话说,你的程序运行如下:

  • 进入循环
  • 锁定互斥锁
  • 调用函数
  • 增加 j 的值
  • 打印值
  • 解锁互斥锁

解决这个问题的方法是将值按值传递给函数,而不是在 goroutine 之间共享它们。

英文:

When you execute this :

  1. go func() {
  2. fmt.Printf(&quot;%d + %d = %d\n&quot;, i, j, j+i)
  3. mutex.Unlock()
  4. }()

The current goroutine makes another loop which increments j.

Incrementation takes place before printf that is why even thought the function was called while
j was &lt; 5 it could be increased to 5 before the function had time to print out values.
In other words you program runs like this :

  • Enter loop
  • Lock Mutex
  • Call func()
  • Increment j
  • Print values
  • Unlock Mutex

Solution to this would be to pass the values by value to the function instead of sharing them throughout goroutines.

huangapple
  • 本文由 发表于 2017年2月10日 23:21:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/42162879.html
匿名

发表评论

匿名网友

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

确定