Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync: Once uses, but doesn't need atomic operations #22104

Closed
JelteF opened this issue Oct 2, 2017 · 12 comments
Closed

sync: Once uses, but doesn't need atomic operations #22104

JelteF opened this issue Oct 2, 2017 · 12 comments

Comments

@JelteF
Copy link
Contributor

JelteF commented Oct 2, 2017

What version of Go are you using (go version)?

1.9

Does this issue reproduce with the latest release?

Yes, code is here: https://github.com/golang/go/blob/master/src/sync/once.go#L35-L46

What did you expect to see?

To see synchronised access to o.done, but not excessively synchronising.

What did you see instead?

It's synchronised using atomic load and store and using a mutex.

Proposed fix

As far as I can tell there is no reason why load and store to o.done have to be atomic. The write is already synchronised using the mutex and before running f a second time the value of o.done is checked inside the mutex. So my fix would simply be to use non atomic operations on o.done.

I don't mind creating a PR myself, but would like to be sure that I'm not overlooking something before doing so.

@davecheney
Copy link
Contributor

davecheney commented Oct 2, 2017 via email

@JelteF
Copy link
Contributor Author

JelteF commented Oct 2, 2017

I was super confused at first that the exact oposite change would have an advantage. But after looking more closely it adds both the fast path and the atomic operation in the same PR. What I'm suggesting is doing the fast path change without the atomic operation, just do it non atomic (maybe with a boolean like it was before).

@JelteF
Copy link
Contributor Author

JelteF commented Oct 2, 2017

Related question, are there docs somewhere on how to run these benchmarks myself?

@davecheney
Copy link
Contributor

@JelteF

What I'm suggesting is doing the fast path change without the atomic operation, just do it non atomic (maybe with a boolean like it was before).

that would potentially introduce a data race

Related question, are there docs somewhere on how to run these benchmarks myself?

These are standard Go testing benchmarks.

@randall77
Copy link
Contributor

The atomic load is required. Without it, the second caller of sync.Once may not see all of the modifications done by the first call to sync.Once.

@skabbes
Copy link

skabbes commented Oct 2, 2017

@randall77 I believe in this case stale reads are OK since the slow path actually has to relock and recheck anyway. This is not to say that there isn't a race there, but I believe on modern platforms with support for hardware atomic read / write with 32bit memory this would be impossible.

@JelteF I think you'd need some sort of atomic.LoadUint32RelaxedMemoryOrdering() operation if you wanted this to be race free... on all platforms.

@skabbes
Copy link

skabbes commented Oct 2, 2017

Oh wow, and also need to have correct alignment for atomic operations.

I think the added complexity isn't worth it, it isn't the go way.

@randall77
Copy link
Contributor

@skabbes: You're right that the atomic load is not required for the slow path. But the atomic load is required for the fast path, when it reads a 1 and immediately returns. There needs to be some synchronization so that the second+ callers of Once.Do can see all the writes that f() did in the first call to Once.Do.

@skabbes
Copy link

skabbes commented Oct 2, 2017

Completely didn't think about that as an API guarantee, but it definitely is. Kudos.

@davecheney
Copy link
Contributor

Thank you for the discussion. I’m going to close this as there is no obvious change required.

If you wish to continue to discuss this, please see https://golang.org/wiki/Questions for good places to ask. Thanks.

@JelteF
Copy link
Contributor Author

JelteF commented Oct 2, 2017

Thanks for the feedback everyone. I learnt something new about dataraces, namely that they are possible even when the read and assignment is guarded by a mutex. I checked out moving the atomic load to the slow instead of the fast path and the race detector was still complaining. Even with your comments it's still not clear to me why that would not be a valid solution.

@davecheney
Copy link
Contributor

@JelteF let's take this off the issue tracker, please see https://golang.org/wiki/Questions for good places to ask. Thanks.

@golang golang locked and limited conversation to collaborators Oct 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants