Updating the Go memory model #47141
Replies: 15 comments 51 replies
-
A hearty +1 to atomic.Bool and friends. |
Beta Was this translation helpful? Give feedback.
-
There's a minor typo in a code sample towards the end: i := *p
if i < 0 || i >= len(fns) {
panic("invalid function index")
}
... complex code ...
// compiler must NOT reload i = *p here
funcs[i]() The call to That aside, the proposal looks great. I too would prefer not to add unsynchronized atomics. |
Beta Was this translation helpful? Give feedback.
-
@rsc, I don't quite follow this:
I see a specific issue with |
Beta Was this translation helpful? Give feedback.
-
I like the idea of being clear about what kind of invalid programs are allowed in the case when you have a data-race and how they might behave. I also like the call-out that concurrent multi-word reads/writes allow you to observe inconsistent (and potentially corrupt) structures. One thing that I think could be more explicit is the behaviour when you have concurrent reads and writes to a memory location X that is smaller than (or equal in size to) a machine-word.
I think either of the following options make sense, and experimentally the first seems true (and definitely the most expected!):
|
Beta Was this translation helpful? Give feedback.
-
In the list of ordering of operations, the word "happens" seems imprecise:
If you read "happens" to mean "begins and ends," then it would seem to be impossible for a read to end before the corresponding send completes. Maybe "happens" should be "begins," if that's the correct semantics. |
Beta Was this translation helpful? Give feedback.
-
Some minor comments from the first pass. s/Like in Java/As in Java/ Two bullet points in "Go's memory model today" are mutually incompatible:
Did you miss a "buffered" in the first bullet? s/This section list/&s/ In the summary in the next section, s/useful/practical/. The definition of data race doesn't feel quite right to me. Maybe concurrently is the issue. Try this: A data race is defined as a write to a memory location happening while another read or write to that same location is also happening. I like the point about encouraging external (please don't say third-party; I don't even know who the second party is) packages documenting their semantics. Do you need to say more about the operations around atomic operations? I think there is a happens-before claim you can make about code on either side of an atomic update, but I'm not sure. What you have there now talks only about the atomics, not the rest. Look at:
What can you say about In "C++ provides both, of course", drop "of course". No need to editorialize. Outside the parens, say "barriers" not "fences" I would argue strongly against adding unsynchronized atomics, to keep with the philosophy of enough is enough, and the subtlety of adding more operations with different semantics. Where would you document the disallowed compiler optimizations? Also, can you find a categorical definition instead of a catalog of bad examples? |
Beta Was this translation helpful? Give feedback.
-
Possibly related: cache alignment and false sharing. It would be great if there was a way to cause cache aligned allocation so as to avoid false sharing when concurrent access occurs. There are some work arounds, such as adding padding, to ensure one thing is not on the same cache line as another, but having more precision, and especially having a way to align the start of a struct, would be very helpful. |
Beta Was this translation helpful? Give feedback.
-
While I agree with the statement about the loss of consistency of internal pointer, length and pointer, type pairs I would like to see a rationale for it, even though it may be obvious. Not being an expert I guess consistency would require synchronization, which would lead to higher memory requirements and performance penalties and would break programs relying on the current internal representation of the affected data structures and the benefit would be small because parallel access to multiple fields of a structure or multiple variables would still be inconsistent. |
Beta Was this translation helpful? Give feedback.
-
Hi @rsc I don't quite understand this sentence "If the effect of an atomic operation A is observed by atomic operation B, then A happens before B.", in my opinion this is obvious, I don't know if I miss some points. Could you elaborate ? thanks |
Beta Was this translation helpful? Give feedback.
-
Thank for all of your work on this. I also really loved the other blog posts in that series. I have a question about the goals for Go's memory model in the case of races, and how these goals differ from Javascript's ( for my future reference: https://tc39.es/ecma262/#sec-memory-model ). At first glance it sounds like Go wants the same as what Javascript wants; both Javascript and Go eschew "DRF or catch fire", and, like Javascript, you also say that with Go "programs with data races have defined semantics with a limited number of outcomes". But you also imply that Go wants to sit in-between Javascript and C in some fashion. Is it accurate to say that you want less specification complexity than Javascript, and in exchange you are willing to pay the price of more formal ambiguity in the following form: sometimes Go will permit a variable to take on a 'paradoxical' value, when this would have been ruled out by Javascript's more extensive formal semantics? |
Beta Was this translation helpful? Give feedback.
-
The proposal describes the happens-before relationships that sync/atomic creates. It also, less formally, says that memory locations that are accessed with sync/atomic must never be accessed without sync/atomic. (The next section says "Whether a particular value should be accessed with atomics is therefore a property of the value and not of a particular access.") If we can arrange for happens-before relationships that eliminate racy access to a memory location that was previously accessed with sync/atomic, can a correct program later do a non-atomic read of that location? Can a correct program do non-atomic initialization of locations that are later accessed with sync/atomic? I think the answer is "yes, that's allowed and will work according to the usual happens-before rules". But the way that sync/atomic is discussed—both in the current memory model and with the proposed changes—makes it seem like those operations somehow taint the memory location and make the normal set of happens-before rules not completely describe the situation. https://play.golang.org/p/Cb73lX6ZPuR
|
Beta Was this translation helpful? Give feedback.
-
As discussed below, it would probably be preferable if the compiler would just automatically align the dedicated atomic types to fit the alignment guarantees. |
Beta Was this translation helpful? Give feedback.
-
I have a module which makes significant use of atomic Booleans. Having atomic.Bool would simplify some parts of its implementation, but it is insufficient to fully replace how I use atomics in it. In particular, there are situations in which I need to inspect and update multiple flags simultaneously. This code had me on the verge of writing a proposal for an atomic.Flags type, providing bitwise rather than arithmetic operations on either uint8 or uint32. I think that such a type would be valuable even if atomic.Bool comes to exist, but there is obviously overlap between a type that describes one Boolean and a type that describes a set of related Booleans. Maybe it's time to write that proposal. |
Beta Was this translation helpful? Give feedback.
-
I think if I were reading the Go Memory Model page without having read much about memory models and data races before I would find this section to be a little misleading:
Programs with data races may also be "invalid" in another less formal sense, which is that they can no longer rely on the language performing to spec. To give the example from elsewhere in the doc, a racing write to a map could corrupt arbitrary memory, which sure doesn't sound like "defined semantics with a limited number of outcomes"! If I understand correctly, that phrase refers to each individual memory read/write, but that may not be clear to someone who is not familiar with the underlying issues/history. |
Beta Was this translation helpful? Give feedback.
-
I found the proposal missed a discussion regarding the
If the compiler reorders "x.y = &i" and "*i = 42". This result can be unexpected, which falls into the category of compiler optimization rules. @ianlancetaylor also suggested a description "Given a pointer p, there is no possible ordering such that the finalizer happens-before any read or write of *p." because the inverse of the statement is too strong that prevents possible compiler optimization. Maybe this is more understandable (?): Given a pointer |
Beta Was this translation helpful? Give feedback.
-
I posted a blog post Updating the Go memory model about some changes I am planning to propose regarding Go's memory model.
This discussion is for collecting feedback about those changes before filing an official proposal.
Please make good use of the limited threading below: post a top-level comment for something new and use the reply feature to reply to someone else's comment. We hope this will help keep discussion manageable.
Thank you!
Beta Was this translation helpful? Give feedback.
All reactions