我目前正在学习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
(目前是最新版本)
我的问题:
atomic.AddInt64
方法,对吗?任何帮助将不胜感激:)
答案 0 :(得分:7)
您的代码中没有任何内容,所以这就是为什么种族检测器什么也没检测到。您始终可以通过已启动的goroutine中的atomic
程序包访问counter
变量,而不能直接访问。
之所以有时获得1000有时甚至更少的原因是由于运行goroutines的活动线程数:GOMAXPROCS
。在Go Playground上,它是1,因此任何时候您都可以使用一个活动的goroutine(因此基本上您的应用程序是按顺序执行的,没有任何并行性)。并且当前的goroutine调度程序不会将goroutine随意停放。
在本地计算机上,您可能有一个多核CPU,并且GOMAXPROCS
默认为可用逻辑CPU的数量,因此GOMAXPROCS
大于1,因此您有多个并行运行的goroutine。 (实际上是并行的,而不仅仅是 concurrent )。
请参见以下片段:
v := atomic.LoadInt64(&counter)
v++
atomic.StoreInt64(&counter, v)
加载counter
的值并将其分配给v
,递增v
,然后回存递增的v
的值。如果两个并行goroutine同时执行此操作会怎样?假设两者都加载了值100
。两者都增加其本地副本:101
。两者都应写回101
,即使应该写在102
上。
是的,原子地递增计数器的正确方法是像这样使用atomic.AddInt64()
:
for i := 0; i < num; i++ {
go func() {
atomic.AddInt64(&counter, 1)
wg.Done()
}()
}
通过这种方式,无论GOMAXPROCS
是什么,您都将始终获得1000。