Fine-tune sync.Once
Closed this issue · 2 comments
Please answer these questions before submitting your issue. Thanks!
What version of Go are you using (go version)?
go version go1.7 darwin/amd64
What operating system and processor architecture are you using (go env)?
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/wang/dev/"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/cf/kfbz84t902x4ssxy84rw2m0w0000gn/T/go-build294559391=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
What did you do?
If possible, provide a recipe for reproducing the error.
A complete runnable program is good.
A link on play.golang.org is best.
Fine-tune sync.Once so that it's faster. Modify Do method of sync.Once as following:
func (o *Once) Do(f func()) {
if atomic.LoadInt32(&o.done) == 1 {
return
}
o.m.Lock()
if o.done == 0 {
atomic.StoreInt32(&o.done, 1)
o.m.Unlock()
f()
return
}
o.m.Unlock()
}The result of bench testing:
Benchmark_StdOnce-4 1000000000 2.02 ns/op
Benchmark_MyOnce-4 2000000000 1.53 ns/opThe benchmark code as following:
func onceFunc() {
}
func Benchmark_StdOnce(b *testing.B) {
once := new(sync.Once)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
once.Do(onceFunc)
}
})
}
func Benchmark_MyOnce(b *testing.B) {
once := new(Once)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
once.Do(onceFunc)
}
})
}And if f() costs a lot of time, current implemtation of once.Do would hold the mutex a long time.
What did you expect to see?
What did you see instead?
sync.Once currently guarantees that f is done (either returned or paniced) after a call to Do returns.
With your change Do can return before f is done or even before f is called (the goroutine calling f can be descheduled before the call, but after the atomic store or after unlocking the mutex). The comment for Do even says
Because no call to Do returns until the one call to f returns if f causes Do to be called, it will deadlock.
So this would clearly be a breaking change and is outside the scope for Go 1.
Thanks!!