英文:
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
程序已退出。
看到输出结果,我对以下几点感到惊讶:
j
没有出现 '1'j
出现了 '5'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 (
"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()
}()
}
}
}
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:
- There are no '1's for the
j
- There are '5's for the
j
- 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时传入i
和j
的值,并且后续的迭代不会影响它: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("%d + %d = %d\n", 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("%d + %d = %d\n", 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 < 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论