为什么竞争检测器没有检测到这种竞争条件?
Why is the race detector not detecting this race condition?
我目前正在学习 Go 编程语言,我现在正在试验 atomic 包。
在这个例子中,我生成了许多 Goroutine,它们都需要增加一个包级变量。有几种方法可以避免竞争条件,但现在我想使用 atomic
包来解决这个问题。
当 运行 在我的 Windows PC (go run main.go
) 上执行以下代码时,结果不是我期望的结果(我希望最终结果是 1000)。最终数字介于 900 和 1000 之间。当 运行 Go Playground 中的代码时,该值为 1000。
这是我使用的代码:https://play.golang.org/p/8gW-AsKGzwq
var counter int64
var wg sync.WaitGroup
func main() {
num := 1000
wg.Add(num )
for i := 0; i < num ; i++ {
go func() {
v := atomic.LoadInt64(&counter)
v++
atomic.StoreInt64(&counter, v)
// atomic.AddInt64(&counter, 1)
// fmt.Println(v)
wg.Done()
}()
}
wg.Wait()
fmt.Println("final", counter)
}
go run main.go
final 931
go run main.go
final 960
go run main.go
final 918
我原以为竞争检测器会给出错误,但它没有:
go run -race main.go
final 1000
它输出正确的值 (1000)。
我使用的是go版本go1.12.7 windows/amd64
(目前最新版本)
我的问题:
- 为什么竞争检测器没有给出错误,但是当 运行 没有竞争检测器的代码时我看到了不同的值?
- 我认为 Load/Store 组合不起作用的原因是这两个原子调用作为一个整体不是原子的。在这种情况下,我应该使用
atomic.AddInt64
方法,对吗?
任何帮助将不胜感激:)
您的代码中没有任何内容,这就是竞争检测器未检测到任何内容的原因。您的 counter
变量总是通过启动的 goroutines 中的 atomic
包访问,而不是直接访问。
有时你得到 1000 而有时更少的原因是由于 运行 goroutines 的活动线程数:GOMAXPROCS
。在 Go Playground 上它是 1,所以在任何时候你都有一个活动的 goroutine(所以基本上你的应用程序是顺序执行的,没有任何并行性)。而且现在的goroutine scheduler不会把goroutines任意停放。
在你的本地机器上你可能有一个多核 CPU,并且 GOMAXPROCS
默认为可用逻辑 CPU 的数量,所以 GOMAXPROCS
大于 1 ,所以你有多个 goroutines 运行ning parallel(真正的并行,而不仅仅是 concurrent)。
查看此片段:
v := atomic.LoadInt64(&counter)
v++
atomic.StoreInt64(&counter, v)
加载 counter
的值并将其分配给 v
,递增 v
,然后存储回递增的 v
的值。如果 2 个并行 goroutines 同时执行此操作会怎样?假设两者都加载值 100
。两者都递增其本地副本:101
。两者都写回 101
,即使它应该在 102
.
是的,以原子方式递增计数器的正确方法是像这样使用 atomic.AddInt64()
:
for i := 0; i < num; i++ {
go func() {
atomic.AddInt64(&counter, 1)
wg.Done()
}()
}
无论 GOMAXPROCS
是多少,这样你总能得到 1000。
我目前正在学习 Go 编程语言,我现在正在试验 atomic 包。
在这个例子中,我生成了许多 Goroutine,它们都需要增加一个包级变量。有几种方法可以避免竞争条件,但现在我想使用 atomic
包来解决这个问题。
当 运行 在我的 Windows PC (go run main.go
) 上执行以下代码时,结果不是我期望的结果(我希望最终结果是 1000)。最终数字介于 900 和 1000 之间。当 运行 Go Playground 中的代码时,该值为 1000。
这是我使用的代码:https://play.golang.org/p/8gW-AsKGzwq
var counter int64
var wg sync.WaitGroup
func main() {
num := 1000
wg.Add(num )
for i := 0; i < num ; i++ {
go func() {
v := atomic.LoadInt64(&counter)
v++
atomic.StoreInt64(&counter, v)
// atomic.AddInt64(&counter, 1)
// fmt.Println(v)
wg.Done()
}()
}
wg.Wait()
fmt.Println("final", counter)
}
go run main.go
final 931
go run main.go
final 960
go run main.go
final 918
我原以为竞争检测器会给出错误,但它没有:
go run -race main.go
final 1000
它输出正确的值 (1000)。
我使用的是go版本go1.12.7 windows/amd64
(目前最新版本)
我的问题:
- 为什么竞争检测器没有给出错误,但是当 运行 没有竞争检测器的代码时我看到了不同的值?
- 我认为 Load/Store 组合不起作用的原因是这两个原子调用作为一个整体不是原子的。在这种情况下,我应该使用
atomic.AddInt64
方法,对吗?
任何帮助将不胜感激:)
您的代码中没有任何内容,这就是竞争检测器未检测到任何内容的原因。您的 counter
变量总是通过启动的 goroutines 中的 atomic
包访问,而不是直接访问。
有时你得到 1000 而有时更少的原因是由于 运行 goroutines 的活动线程数:GOMAXPROCS
。在 Go Playground 上它是 1,所以在任何时候你都有一个活动的 goroutine(所以基本上你的应用程序是顺序执行的,没有任何并行性)。而且现在的goroutine scheduler不会把goroutines任意停放。
在你的本地机器上你可能有一个多核 CPU,并且 GOMAXPROCS
默认为可用逻辑 CPU 的数量,所以 GOMAXPROCS
大于 1 ,所以你有多个 goroutines 运行ning parallel(真正的并行,而不仅仅是 concurrent)。
查看此片段:
v := atomic.LoadInt64(&counter)
v++
atomic.StoreInt64(&counter, v)
加载 counter
的值并将其分配给 v
,递增 v
,然后存储回递增的 v
的值。如果 2 个并行 goroutines 同时执行此操作会怎样?假设两者都加载值 100
。两者都递增其本地副本:101
。两者都写回 101
,即使它应该在 102
.
是的,以原子方式递增计数器的正确方法是像这样使用 atomic.AddInt64()
:
for i := 0; i < num; i++ {
go func() {
atomic.AddInt64(&counter, 1)
wg.Done()
}()
}
无论 GOMAXPROCS
是多少,这样你总能得到 1000。