- 最后输出
num
的值可能是多少?
num
的值可能小于10,并且是0到10之间的任意值(包含0和10)。这是因为多个goroutine
并发执行num++
操作时,会出现竞态条件(race condition)。由于num++
并非原子操作,在并发环境下,不同goroutine
对num
的读、修改和写操作可能会相互干扰。例如,一个goroutine
读取num
的值为5,在它还没来得及将num
更新为6时,另一个goroutine
也读取了num
的值5,然后两个goroutine
都将num
更新为6,这样就丢失了一次num
的递增操作。
- 为什么?
- 原因就是Go语言中的普通变量在并发读写时没有自动保护机制。
num++
操作实际上分为三个步骤:读num
的值到寄存器,在寄存器中对值加1,再将寄存器中的值写回num
。在多goroutine
并发执行时,这些步骤可能会交叉进行,导致结果不可预测。
- 如何确保得到预期的结果?
package main
import (
"fmt"
"sync"
)
func main() {
var num int = 0
var mu sync.Mutex
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
num++
mu.Unlock()
}()
}
wg.Wait()
fmt.Println(num)
}
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var num int64 = 0
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt64(&num, 1)
}()
}
wg.Wait()
fmt.Println(num)
}
- 这里涉及到闭包在并发环境下的哪些特性?
- 共享变量:闭包可以访问和修改其外层函数作用域中的变量,如上述代码中闭包函数可以访问和修改
num
变量。在并发环境下,这种对共享变量的访问如果不加以控制就会出现竞态条件。
- 延迟绑定:闭包函数在定义时不会立即绑定外层作用域变量的值,而是在运行时绑定。例如,在
for
循环中创建闭包,闭包中对循环变量i
的访问在运行时才确定其值。如果在闭包运行时i
的值已经发生了变化,可能会导致不符合预期的结果。在上述代码中虽然没有直接体现i
的延迟绑定问题,但在更复杂的场景下,这也是需要注意的特性。