数据竞争,两个 goroutines 加上相同的 val
data-race, two goroutines plus same val
考虑下面的代码,在我看来,val 将在 100 到 200 之间,但它总是 200
var val = 0
func main() {
num := runtime.NumCPU()
fmt.Println("使用cpu数量", num)
go add("A")
go add("B")
time.Sleep(1 * time.Second)
fmt.Println("val的最终结果", val)
}
func add(proc string) {
for i := 0; i < 100; i++ {
val++
fmt.Printf("execute process[%s] and val is %d\n", proc, val)
time.Sleep(5 * time.Millisecond)
}
}
为什么 val 最后总是 200?
增加整数只需要几纳秒的数量级,而每个 goroutine 在每次增加之间等待 5 毫秒。
意思是,每个 goroutine 只花费大约 1,000,000 的时间实际执行操作,其余时间都在休眠。因此,发生干扰的可能性非常低(因为两个 goroutine 需要同时进行操作)。
即使两个 goroutines 在 相同的计时器上,time
库的实际精度也远不及持续产生碰撞所需的纳秒级。另外,goroutines 正在做一些打印,这将进一步分散时间。
正如评论中所指出的,您的代码仍然存在数据竞争(这似乎是您的意图),这意味着尽管有任何观察,但我们无法对您程序的输出做出任何肯定的判断。它可能会输出 100-200 之间的任何数字,或完全不同的数字。
您的代码有 2 个问题:
- 您有一场数据竞争 - 在没有同步的情况下同时写入和读取
val
。它的存在使得对程序结果的推理变得毫无意义。
- 1 秒
main()
的休眠时间太短 - goroutines 可能在 1 秒后还没有完成。您预计 fmt.Printf
根本不需要时间,但控制台输出确实需要大量时间(在某些 OS 上比其他时间更长)。所以这个循环不会花费 100 * 5 = 500 毫秒,而是要长得多。
这是一个固定版本,它自动递增 val
并正确等待两个 goroutine 完成,而不是假设它们将在 1 秒内完成。
var val = int32(0)
func main() {
num := runtime.NumCPU()
fmt.Println("使用cpu数量", num)
var wg sync.WaitGroup
wg.Add(2)
go add("A", &wg)
go add("B", &wg)
wg.Wait()
fmt.Println("val的最终结果", atomic.LoadInt32(&val))
}
func add(proc string, wg *sync.WaitGroup) {
for i := 0; i < 100; i++ {
tmp := atomic.AddInt32(&val, 1)
fmt.Printf("execute process[%s] and val is %d\n", proc, tmp)
time.Sleep(5 * time.Millisecond)
}
wg.Done()
}
考虑下面的代码,在我看来,val 将在 100 到 200 之间,但它总是 200
var val = 0
func main() {
num := runtime.NumCPU()
fmt.Println("使用cpu数量", num)
go add("A")
go add("B")
time.Sleep(1 * time.Second)
fmt.Println("val的最终结果", val)
}
func add(proc string) {
for i := 0; i < 100; i++ {
val++
fmt.Printf("execute process[%s] and val is %d\n", proc, val)
time.Sleep(5 * time.Millisecond)
}
}
为什么 val 最后总是 200?
增加整数只需要几纳秒的数量级,而每个 goroutine 在每次增加之间等待 5 毫秒。
意思是,每个 goroutine 只花费大约 1,000,000 的时间实际执行操作,其余时间都在休眠。因此,发生干扰的可能性非常低(因为两个 goroutine 需要同时进行操作)。
即使两个 goroutines 在 相同的计时器上,time
库的实际精度也远不及持续产生碰撞所需的纳秒级。另外,goroutines 正在做一些打印,这将进一步分散时间。
正如评论中所指出的,您的代码仍然存在数据竞争(这似乎是您的意图),这意味着尽管有任何观察,但我们无法对您程序的输出做出任何肯定的判断。它可能会输出 100-200 之间的任何数字,或完全不同的数字。
您的代码有 2 个问题:
- 您有一场数据竞争 - 在没有同步的情况下同时写入和读取
val
。它的存在使得对程序结果的推理变得毫无意义。 - 1 秒
main()
的休眠时间太短 - goroutines 可能在 1 秒后还没有完成。您预计fmt.Printf
根本不需要时间,但控制台输出确实需要大量时间(在某些 OS 上比其他时间更长)。所以这个循环不会花费 100 * 5 = 500 毫秒,而是要长得多。
这是一个固定版本,它自动递增 val
并正确等待两个 goroutine 完成,而不是假设它们将在 1 秒内完成。
var val = int32(0)
func main() {
num := runtime.NumCPU()
fmt.Println("使用cpu数量", num)
var wg sync.WaitGroup
wg.Add(2)
go add("A", &wg)
go add("B", &wg)
wg.Wait()
fmt.Println("val的最终结果", atomic.LoadInt32(&val))
}
func add(proc string, wg *sync.WaitGroup) {
for i := 0; i < 100; i++ {
tmp := atomic.AddInt32(&val, 1)
fmt.Printf("execute process[%s] and val is %d\n", proc, tmp)
time.Sleep(5 * time.Millisecond)
}
wg.Done()
}