-
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
sync/atomic: add typed atomic values #50860
Comments
Change https://golang.org/cl/381317 mentions this issue: |
[Sorry, I didn't notice the "DO NOT REVIEW" in the CL and added this comment there where it wasn't possible to remove] Absolutely. Pointer in particular makes life so much easier. I wonder if it might be worth declaring a generic interface that covers all of those new types:
(I don't know what the preferred spelling for "Of" types is these days). Unfortunately these types aren't automatically assignable to that interface (cf #41176) but I think the type is still useful (and makes it clear that they all conform to the same pattern too). |
One other thought: under this proposal you have to use a different type for an value that's atomically accessed.
|
Re LoadPointerOf, etc, I'd rather take the conservative route and wait until there is a significant, demonstrated need. I think the different type will be correct for the vast majority of uses, and part of the goal of the new API is to get people to think of each value as either always atomic or never atomic. That's not strictly required, but it helps avoid bugs, so I don't want to make it easier to backslide on that. |
To me, one of the advantages of this proposal is that by defining the atomic types independently it sidesteps the potential alignment issues (#19057, #27577) surrounding atomic access of non-atomic types. I think that's a strong benefit, and would rather not give it up. However, I do think it would be reasonable to define when |
I assume this code is valid: func CompareAndSwapPointerOf[T any](addr **T, old, new *T) (swapped bool) {
return atomic.CompareAndSwapPointer(
(*unsafe.Pointer)(unsafe.Pointer(addr)),
unsafe.Pointer(old),
unsafe.Pointer(new),
)
} I.e., I assume that there is no difference in representation between a pointer to a concrete type and a pointer to a type parameter type. Then, the implementations of those functions are straightforward, so it is easy for packages to define them when they need them. |
For internal implementation reasons, the runtime itself uses a separate atomic package from To provide some concrete examples of use you can take a look at the relation chain on https://go.dev/cl/356169. My personal experience with these thus far has been very positive. Having atomic types makes it difficult/impossible to forget to use an atomic access, which was very easy before. Additionally, at least in the runtime nearly all of our atomic values are basic integral types, so lack of generics doesn't feel too limiting and in fact makes them simpler to type. |
+1 to resolving atomic alignment issues here, and IMO it should happen sooner rather than later. I don't think we should wait for a general alignment-fixing proposal to land (like #19057) before fixing it, because it means users can rewrite their atomic code to actually work straight out of the gate, and I think it's going to take substantially more time to land any kind of general fix to the alignment issues. Also, +1 to a generic |
Complete agreement about arranging, through some kind of magic, that atomic.Int64/atomic.Uint64 carry proper alignment. |
I assume you mean things like converting a *int32 to a *atomic.Int32. I am not sure I really want to guarantee any conditions about safety of that conversion, even though I did use it in the test. (Tests are allowed to assume things about their own package's internals!) Saying anything about that conversion seems like taking on a constraint for little benefit, and it points people in a generally bad direction we'd rather they didn't go. People who need mixed atomic and non-atomic access can still use the old API, which is not going away. |
This proposal has been added to the active column of the proposals project |
@rsc please note that there has been prior work on this also suggesting such an API before https://pkg.go.dev/github.com/nightlyone/atomic |
Does anyone object to accepting this proposal? |
Based on the discussion above, this proposal seems like a likely accept. |
No change in consensus, so accepted. 🎉 |
This is very similar to https://github.com/uber-go/atomic. One thing they did with that library was providing support for JSON marshal/unmarshal, by calling
Is there any problem with doing that here? |
@flowchartsman, the |
Ah, good point. I suppose a simple wrapper will be enough to get the best of both worlds when needed. |
Change https://go.dev/cl/410127 mentions this issue: |
Change https://go.dev/cl/410131 mentions this issue: |
For #50860. Change-Id: I8e117f00c5da230d0dc398aaed417fe5e64a5b22 Reviewed-on: https://go-review.googlesource.com/c/go/+/410127 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Michael Pratt <mpratt@google.com> Reviewed-by: Michael Knyszek <mknyszek@google.com>
After CL 381317 there exist values that may have an alignment greater than the pointer size for that platform. Specifically, atomic.{Ui|I}nt64 may be aligned to 8 bytes on a 32-bit platform. If such a value, or a container for the value, gets stack-allocated, it's possible that it won't be aligned correctly, because the maximum alignment we enforce on stacks is governed by the pointer size. Changing that would be a significant undertaking, so just escape these values to the heap instead, where we're sure they'll actually be aligned correctly. Change is by rsc@, I'm just shepherding it through code review. For #50860. Change-Id: I51669561c0a13ecb84f821020e144c58cb528418 Reviewed-on: https://go-review.googlesource.com/c/go/+/410131 Run-TryBot: Michael Knyszek <mknyszek@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
One of the reasons for JSON support on the objects is that it's easy for someone to attempt to serialize a struct that has an atomic field and get meaningless output (likely
I lean (2). (I can make a separate proposal for this if you think this warrants a longer discussion.) |
Hey @rsc! The new atomic types are pretty sweet. Thanks for adding those. Q: Are there any plans to add a method similar to C/C++'s |
Change https://go.dev/cl/428362 mentions this issue: |
Updates #50860 Change-Id: I65bced707e50364b16edf4b087c541cf19bb1778 Reviewed-on: https://go-review.googlesource.com/c/go/+/428362 Run-TryBot: Cuong Manh Le <cuong.manhle.vn@gmail.com> Reviewed-by: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Bryan Mills <bcmills@google.com> Auto-Submit: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Is |
Yes. |
In June 2021 I posted a series of articles about memory models, ending with an article about changes I thought we should make to the Go memory model. See https://research.swtch.com/mm especially https://research.swtch.com/gomm.
Then I opened a GitHub Discussion to discuss these changes; see #47141.
Based on that discussion, I propose to add the following types to sync/atomic: Bool, Int32, Int64, Uint32, Uint64, Uintptr, Pointer[T any].
These have methods appropriate to the type. They all have Load, Store, Swap, and CompareAndSwap methods. The integers also have an Add method (Bool and Pointer[T] do not).
The exact details can be viewed in pending CL 381317 prepared for concreteness.
I have filed a separate proposal, #50859, for documentation fixes arising from the June discussion.
Generics? A natural question is why the types are not something like atomic.Val[bool], atomic.Val[int32], and so on. The main answer is that the APIs are different for different types, and generics provides no way to accomplish that.
Specifically, Bool and Pointer[T] should not have an Add method, while the integers should.
Another reason is that there is no way to write a single constraint that works for this set of types. The way to write a generic pointer constraint, for atomic.Val[*byte], is to say [T ~*E, E any], but there is no way to add on the other types. If we do
then any use that infers bool, int32, or so on for T will not have any idea what to infer for E, requiring the programmer to write
where DOESNOTMATTER is any type at all. All in all, trying to use generics is pretty awkward here.
Uses are awkward too: atomic.Val[int32] is not as nice as plain atomic.Int32.
We could potentially introduce a single generic for the ints, as in atomic.Int[int32], but that removes awkwardness in the implementation at the cost of introducing awkwardness (repetition) at all the call sites. That's usually the wrong tradeoff, including here.
(Finally there is the matter of what the implementation body would look like in the generic functions, but all the preceding concerns prevent us from even reaching that one.)
The non-generic API is simpler to explain and easier to use.
The text was updated successfully, but these errors were encountered: