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

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

Mutex within loop leads to unexpected output

问题

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

package main

import (
	"fmt"
	"sync"
)

func main() {
	mutex := new(sync.Mutex)

	for i := 1; i < 5; i++ {
		for j := 1; j < 5; j++ {
			mutex.Lock()
			go func() {
				fmt.Printf("%d + %d = %d\n", i, j, j+i)
				mutex.Unlock()
			}()
		}
	}
}

它产生以下输出:

1 + 2 = 3
1 + 3 = 4
1 + 4 = 5
2 + 5 = 7
2 + 2 = 4
2 + 3 = 5
2 + 4 = 6
3 + 5 = 8
3 + 2 = 5
3 + 3 = 6
3 + 4 = 7
4 + 5 = 9
4 + 2 = 6
4 + 3 = 7
4 + 4 = 8

程序已退出。

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

  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)

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
)

func main() {
	mutex := new(sync.Mutex)

	for i := 1; i &lt; 5; i++ {
		for j := 1; j &lt; 5; j++ {
			mutex.Lock()
			go func() {
				fmt.Printf(&quot;%d + %d = %d\n&quot;, i, j, j+i)
				mutex.Unlock()
			}()
		}
	}
}

It produces an output like this

1 + 2 = 3
1 + 3 = 4
1 + 4 = 5
2 + 5 = 7
2 + 2 = 4
2 + 3 = 5
2 + 4 = 6
3 + 5 = 8
3 + 2 = 5
3 + 3 = 6
3 + 4 = 7
4 + 5 = 9
4 + 2 = 6
4 + 3 = 7
4 + 4 = 8

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,这样单独的线程就可以读取它。这与你的互斥锁无关,而是与跨线程共享变量有关。如果你这样使用:

go func(i,j int) {
    fmt.Printf("%d + %d = %d\n", i, j, j+i)
    mutex.Unlock()
}(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:

go func(i,j int) {
    fmt.Printf(&quot;%d + %d = %d\n&quot;, i, j, j+i)
    mutex.Unlock()
}(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

当你执行这段代码时:

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

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

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

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

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

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

英文:

When you execute this :

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

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:

确定