-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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: atomic: add (*Value).Swap #20164
Comments
I'm very skeptical that code in crypto/tls is even correct. What is going on there? |
The only time the field is set is immediately before calling The field was added in https://golang.org/cl/30790. CC @agl. |
CL https://golang.org/cl/42137 mentions this issue. |
Yes, it's passing an argument to the once function. https://go-review.googlesource.com/c/42137/ does the same thing with closures if that's useful. |
Not at the moment. If you'd like to shelve this until we find another use-case, that's fine with me. |
…Config. Updates #20164. Change-Id: Ib900095e7885f25cd779750674a712c770603ca8 Reviewed-on: https://go-review.googlesource.com/42137 Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
My two cents I had today a semi-real use case. It was real because I was writing a code where I actually went and looked for Value.Swap because I wanted to use it and was a bit surprised not to find it. However the fact that it is not there didn't blocked me, I my specific use case using simple locks instead of atomic value would most probably be doable without significant performance degradation of the whole system, and if it was critical I would probably be able to either use unsafe.Pointer, or fork atomic.Value. The problem is that any of the solutions (including using locks) seems to be on the same order of complication as implementing Value.Swap. So why not do it? The difference between Value.Set and Value.Swap is probably around 5 lines of code, its not a big burden for the standard library. |
@mlitvin How do you use mutex? From your text it seems that you use mutex on both write side and read side. Value is meant for read-mostly scenarios. In such case, if you need swap, you put mutex only around write side and do Load+Store under the mutex, which effectively gives you Swap without any performance degradation (since it's read-mostly scenario), and code complexity is almost equivalent to Swap since this is all local to write side. This also saves you from potential atomicity violations between multiple writers (which are presumably present, since if there is only 1 writer, then Load+Store is equivalent to Swap). |
@dvyukov, putting mutex only around the write side is a good trick (that I haven't thought of) and I may use it. Thanks! That lead to a different suggestion: assume that we don't think that that the potential uses of Value.Swap justify duplicating or complicating Value.Set. We could add an implementation of Swap based on write side lock, and see if it has users. And based on that we can decide if it deserve to be optimized or not. |
And what if it does not have users? Or, if it has users, but they use it wrongly? |
This is getting into the realm of arguments that cannot be won. Against my
better judgment I'll try to present the counter argument.
1. How many libraries in the wild implemented Value before it was inserted
into the standard library? How many independent implementation in the wild
of atomic.SwapUintptr have you seen?
The sync.atomic package implement atomic operation for 7 types.
It implement Load and Store for all of them. It implement Add for all the 5
of them that have a meaningful interpretation of addition. It implement
Swap only for 6 of them even though the seventh has a clear and valid
semantic (as clear and valid as its store semantic). Looking at it from
this point of view its not guarding against feature sprawl, but protecting
arbitrary Swiss cheese decision.
I'm perfectly aware that there is another point of view: 6 of the types are
just exposing primitive CPU capabilities while Value is another beast all
together (and its API is also different). But then I argue that it is the
job of the standard library to allow developer to ignore the distinction.
2. The "trick" with guarding only the store and not the load with a mutex
is a non-trivial trick. It is trivial to implement but not easy to think
about. IMHO this is exactly the kind of think that should be in the
standard library: It guide developers to write better code than they would
have done without it.
…On Thu, Feb 15, 2018 at 4:26 PM Dmitry Vyukov ***@***.***> wrote:
And what if it does not have users? Or, if it has users, but they use it
wrongly?
This is not so about lines of code or optimization, this is much more
about interfaces. If we start putting stuff into standard library public
interface just to test some hypothesizes, it will grow without a limit with
multiple ways of doing the same thing and with lots of ways which provoke
bugs.
This should be done the other way around -- show dozens of existing
packages in the wild that all duplicate this Swap pattern (which is
perfectly implementable without standard library help), then we can
consider including it into standard library.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#20164 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AGQSfM33pvarmzDzJk_t4JZha_-nO3Dwks5tVD56gaJpZM4NLpWZ>
.
|
Well, at the very least we need to see a range of real cases where Value.Swap would be a good fit. We tried, but we failed. I also tried to do a mental experiment and just image some of such cases, but also failed to find any. Argument that can won here is very simple: show how badly it is needed in real programs. Re 2: Swap will not guide developers to write better code, it will guide developers to write complex code with bugs. If we go this route later we will also need CompareAndSwap which will provoke even more complex and more subtly broken code. Mutex gives both simple code and is complete (allows to do Swap, CompareAndSwap and everything else). If the Mutex on write side trick is useful but non-trivial, we should write better docs and provide examples for this. |
So my use case:
I have a kind of state that is generated by one set of goroutines and
consumed by another. The consumers do non-atomic operation so they cannot
guarantee that the state won't change during their work, but they at least
need to have a consistent state of the state. A reasonable solution is to
treat the state as an immutable object put into atomic.Value.
All you need for this is Store and Load. Pretty standard.
Now things become a bit more complicated, some consumers want to be
pro-active knowing about the state change. Perhaps they want to cancel
operation based on the old state.
We add add a cancel method to the state object that would probably
eventually close a channel (either through canceling a context or
directly), and when ever we set a new state we call it.
This is where it is very easy (and readable) with swap: you just swap the
state object and cancel the old one.
…On Fri, Feb 16, 2018 at 10:20 AM Dmitry Vyukov ***@***.***> wrote:
Well, at the very least we need to see a range of real cases where
Value.Swap would be a good fit. We tried, but we failed. I also tried to do
a mental experiment and just image some of such cases, but also failed to
find any.
Staffing things into any library just in case when we don't see nor can't
imagine any uses for it is wrong.
Argument that can won here is very simple: show how badly it is needed in
real programs.
Re 2: Swap will not guide developers to write better code, it will guide
developers to write complex code with bugs. If we go this route later we
will also need CompareAndSwap which will provoke even more complex and more
subtly broken code. Mutex gives both simple code and is complete (allows to
do Swap, CompareAndSwap and everything else).
If the Mutex on write side trick is useful but non-trivial, we should
write better docs and provide examples for this.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#20164 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AGQSfHrvlwVkD9Ow_-QIgmTwbox_bAz0ks5tVTpdgaJpZM4NLpWZ>
.
|
How do producers create new state? |
I guess that you are heading to the question: "If state is generated by two
producers, how do you know which is the latest?"
Well, I don't, all I want is not to catch fire, the simple swap & cancel
solution guarantee that the system have a consistent (though possible
wrong) idea of what is latest state.
It is true that if I had a way to order state (for example some timestamp)
it would probably be a better idea to explicitly lock, compare and set only
if it is later. One can build a lockless solution based on CompareAndSwap
but I wouldn't advocate that.
…On Fri, Feb 16, 2018 at 11:01 AM Dmitry Vyukov ***@***.***> wrote:
How do producers create new state?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#20164 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AGQSfGqw2JVlzOQgOWUUFDyPp0HAC6Sgks5tVUPmgaJpZM4NLpWZ>
.
|
Yes, if there are multiple producers then they generally must not lose updates from each other, which implies some synchronization between them. And the base state a producer used to create the new state is the old state which then needs to be cancelled. The question still holds: is it actually used in real life anywhere? how widespread is this? |
As I guess those kind of arguments cannot be won.
I tried to look at uses of atomic.SwapUintptr and found about one and a
half. Clearly this function didn't go through the same level of filtering.
But you can always say that one mistake doesn't justify another and in fact
it only prove that adding once redundent feature raises request for others
redundent features.
I guess I'm done.
Thanks again for the lock on store side only trick.
…On Fri, Feb 16, 2018 at 11:34 AM Dmitry Vyukov ***@***.***> wrote:
Yes, if there are multiple producers then they generally must not lose
updates from each other, which implies some synchronization between them.
And the base state a producer used to create the new state is the old state
which then needs to be cancelled.
The question still holds: is it actually used in real life anywhere? how
widespread is this?
Note: this is a very small, tricky and frequently bogus subset of an
already tricky and small subset of synchronization patterns. *And* it is
already almost perfectly handled by Mutex+Load+Store. It's not possible nor
reasonable to support *all* possible cases with a special function
provided for that particular case. That's why we have a general-purpose
language that allows one to build things from other things.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#20164 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AGQSfGDJAjqceR2L2ZaoPV-x5meZRfMUks5tVUuTgaJpZM4NLpWZ>
.
|
No, these arguments can easily be won for something like fmt.Printf and json.Decode, It happened for majority of std lib functionality. You can also find very recent examples if you look at release notes. This is really about this particular case. I don't see logic behind generalizing "SendPacketAndAdjustMyChairIfNotWednesday should not be added to std lib" to "nothing should be added to std lib".
Whatever it was, it's there now. And it does not justify anything in this case, right? I am more than sure we would like to adjust some things in hindsight. But that's the point: adding things is easy, removing -- impossible. |
…Config. Updates golang#20164. Change-Id: Ib900095e7885f25cd779750674a712c770603ca8 Reviewed-on: https://go-review.googlesource.com/42137 Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
…Config. Updates golang#20164. Change-Id: Ib900095e7885f25cd779750674a712c770603ca8 Reviewed-on: https://go-review.googlesource.com/42137 Reviewed-by: Russ Cox <rsc@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
atomic.Value
seems to be the preferred replacement for theatomic.*Pointer
methods in code that avoids packageunsafe
. Unfortunately,atomic.Value
doesn't support swaps, making it much less powerful than the equivalentPointer
methods.When investigating
sync.RWMutex
usage in the standard library (#17973), I discovered anRWMutex
incrypto/tls
guarding two fields,sessionTicketKeys
andoriginalConfig
, that are always updated independently. It's trivial to replacesessionTicketKeys
with anatomic.Value
, butoriginalConfig
needs an atomic swap.More generally, it would be nice if
atomic.Value
were as complete a replacement as possible forunsafe.Pointer
with atomic operations.Adding
CompareAndSwap
was discussed previously (#11260).The major arguments against at the time seem to have been:
unsafe.Pointer
as an alternative. (sync/atomic: atomic.Value doesn't support CompareAndSwap #11260 (comment))To address those points directly:
Swap
, unlikeCompareAndSwap
, does not require comparability. (Personally I think it would be good to addCompareAndSwap
too and simply panic for uncomparable types, but as I don't have a use-case for that I would prefer to keep it out-of-scope for this proposal.)crypto/tls
package.unsafe.Pointer
is strongly discouraged outside of thesync
,runtime
, andreflect
packages.(@dvyukov, @josharian, @OneOfOne, @cespare, @adg, @rsc)
The text was updated successfully, but these errors were encountered: