sync/atomic: go1.8 regression: atomic.AddUint64 is unreliable
Sometimes atomic.AddUint64 has no effect.
Update: @cespare has discovered that the loop containing atomic.AddUint64 has been elided.
Update: @rjeczalik has reported that this behavior also occurs with atomic.StoreUint64 and atomic.CompareAndSwapUint64 functions.
atomic.go:
package main
import (
"fmt"
"runtime"
"sync/atomic"
"time"
)
var a uint64 = 0
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
fmt.Println(runtime.NumCPU(), runtime.GOMAXPROCS(0))
go func() {
for {
atomic.AddUint64(&a, uint64(1))
}
}()
for {
val := atomic.LoadUint64(&a)
fmt.Println(val)
time.Sleep(time.Second)
}
}
For go1.4, go1.5, go1.6, and go1.7:
The atomic.AddUint64(&a, uint64(1)) statement works as expected.
$ go version
go version go1.4-bootstrap-20161024 linux/amd64
go version go1.5.4 linux/amd64
go version go1.6.4 linux/amd64
go version go1.7.5 linux/amd64
$ go build atomic.go && ./atomic
4 4
0
96231831
192599210
289043510
385369439
481772231
578143106
674509741
770966820
867408361
963866833
1060299901
<SNIP>
^C
For go1.8 and go tip:
The atomic.AddUint64(&a, uint64(1)) statement appears to have no effect.
go version go1.8 linux/amd64
go version devel +1e69aef Sat Feb 18 19:01:08 2017 +0000 linux/amd64
go version devel +1e69aef Sat Feb 18 19:01:08 2017 +0000 windows/amd64
$ uname -a
Linux peter 4.8.0-36-generic #36~16.04.1-Ubuntu SMP Sun Feb 5 09:39:57 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
$ go env
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/peter/gopath"
GORACE=""
GOROOT="/home/peter/go"
GOTOOLDIR="/home/peter/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build842347949=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="0"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
$ go build atomic.go && ./atomic
4 4
0
0
0
0
0
0
0
0
0
<SNIP>
^C
Interestingly, we can make go1.8 and go tip work with a simple code modification that should not have any effect:
atomic_pass.go:
package main
import (
"fmt"
"runtime"
"sync/atomic"
"time"
)
var a uint64 = 0
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
fmt.Println(runtime.NumCPU(), runtime.GOMAXPROCS(0))
go func() {
for {
new := atomic.AddUint64(&a, uint64(1))
if new == 0 {
runtime.Gosched() // or print()
}
}
}()
for {
val := atomic.LoadUint64(&a)
fmt.Println(val)
time.Sleep(time.Second)
}
}
$ go version
go version go1.8 linux/amd64
$ go build atomic_pass.go && ./atomic_pass
4 4
0
126492073
253613883
378888824
506065798
633247293
760383560
887553077
1014723419
<SNIP>
^C
After AddUint64(&a, uint64(1), new should be greater than zero and runtime.Gosched() should not be executed. Substituting print() for runtime.Gosched() also works. If the line runtime.Gosched() // or print() is commented out the program reverts to failure.
sync/atomic: go1.8 regression: atomic.AddUint64 is unreliable
Sometimes
atomic.AddUint64has no effect.Update: @cespare has discovered that the loop containing
atomic.AddUint64has been elided.Update: @rjeczalik has reported that this behavior also occurs with
atomic.StoreUint64andatomic.CompareAndSwapUint64functions.atomic.go:
For go1.4, go1.5, go1.6, and go1.7:
The
atomic.AddUint64(&a, uint64(1))statement works as expected.For go1.8 and go tip:
The
atomic.AddUint64(&a, uint64(1))statement appears to have no effect.Interestingly, we can make go1.8 and go tip work with a simple code modification that should not have any effect:
atomic_pass.go:
After
AddUint64(&a, uint64(1),newshould be greater than zero andruntime.Gosched()should not be executed. Substitutingprint()forruntime.Gosched()also works. If the lineruntime.Gosched() // or print()is commented out the program reverts to failure.