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
proposal: sync: add a HasRun method to sync.Once #19245
Comments
This seems ripe for misuse. And even in your example code it looks like you're doing it wrong. Your code permits two goroutines to both see HasRun() return false and then do duplicate initialization work. You should instead do: func (n *Normal) MarginalNormalSingle(i int, src *rand.Rand) distuv.Normal {
n.once.Do(n.initStd)
std := n.std
....
}
func (n *Normal) initStd() {
....
} |
I don't understand. Your |
Sorry, I didn't read all your code. I just responded looking at your final snippet. You can work around this without any new API. Just use atomic.StoreInt32 in your I don't think we want to encourage such racy checks. We also didn't accept Mutex.TryLock in #6123. |
By which you mean replicating the code that's in |
Agreeing with @bradfitz . This should not go in. sync.Once is here exactly to handle concurrent invocations correctly. Every time you provide a simple predicate that doesn't also atomically handle an action depending on the predicate, you are producing racy code in a concurrent environment. If you still wanted that predicate, you can do exactly as Brad suggested: simply use an atomic store to set the predicate in the function you provide to sync.Once. |
Okay. |
Sorry, I thought I understood yesterday, but I don't think I do anymore. @bradfitz says that "my code permits two goroutines to see HasRun() as false and do duplicate initialization work". This is not true. @griesemer : Isn't having I'm not necessarily trying to change the argument, but you both seem to be suggesting that the above code is bad. I don't understand why. It's not a race condition if the check happens atomically, and it increases the efficiency (in big O) for almost all executions of the code. |
@btracey Think about the code you'd write using the HasRun predicate: Even if HasRun returns false, by the time the caller checks the result of HasRun (which is still false), the code may actually have run (and if you'd call HasRun again it would return true) - hence the race condition. |
Right, I understand that. This is why there needs to be protection with an atomic or mutex. The code that uses the answer returns the same number in either condition, it just uses a different means to acquire the number. On the one hand, I can see the argument that this kind of unpredictability is bad, but on the other using If the argument is just "this is too dangerous a tool to expose", then understood. It seemed like there was an additional "and changing your code to use the atomics is a bad idea" and I'm trying to understand why. |
@btracey It's a dangerous tool to expose exactly because it can't be used correctly without yet another synchronization mechanism. So why not use just one mechanism instead? |
Well, with |
In some cases, a different code path should be taken depending on if sync.Once() has been executed (example below). It is possible to work around this issue by using a mutex, but Once can return this information more efficiently
Use case:
In gonum's implementation of a multivariate Normal distribution, we only compute and store the covariance matrix if necessary. Most commonly, users need to compute probabilities and generate random numbers, neither of which actually need the covariance matrix. Computing and storing the matrix is O(n^3) operation and O(n^2) respectively, which can be a significant additional cost. We have a
setSigma
method that uses async.Once
to compute and store the covariance matrix if it's actually required. This is fine, except there are also operations which only need a single element of the covariance matrix, for example getting the marginal distribution along the single dimension. If the covariance matrix has already been computed (due to some other method call), then it's an O(1) operation to return the marginal. If it has not been computed, then a single element of the covariance matrix can be computed in O(n) time, instead of forcing the entire (n^3) operation of the covariance matrix.At the moment, we have the code
These two methods cannot be called concurrently, because checking that
n.sigma == nil
directly races againstn.Once
settingn.sigma
. It is possible to work around this using a mutex, with something likeand a similar transformation to
setSigma
, but it would be really nice to just have theMarginalNormal
code beThe text was updated successfully, but these errors were encountered: