|
|
|
|
|
by twoodfin
958 days ago
|
|
There's no ordering guarantee, just like there's no ordering guarantee across threads for ordinary variables. This is what makes them hard to reason about. C++ makes “as if” ordering guarantees within a single thread trivial: If you can observe things happening out of the order in which your defined-behavior code sequenced them, you have a bug in your compiler or your CPU. Relaxed ordering (on purpose) throws out all implicit sequencing guarantees when threads observe each other. My confusion is that I believed this was all well-understood by implementers and programmers decades ago. That relaxed is harder than sequential to get right was as true on the Alpha as it is on modern ARMs. But this article seems to suggest what should be obvious consequences of relaxed semantics are unexpected & undesirable for C++! |
|
I think we're speaking past each other.
You're implicitly assuming there is some piece of code using atomics, and you're saying it's harder to reason about its behavior when the atomics are made to be relaxed (versus, say, sequentially consistent). That's true enough, but it misses my point.
I'm coming at this from the opposite direction: I have certain kinds of atomics available, and then I'm using them to write the code. In such a scenario, it's quite easy to distinguish between "I might care about ordering" vs. "I don't care about ordering". In the first case, you probably shouldn't use relaxed atomics - good luck figuring out what to use, it may very well be tricky. If you insist on using relaxed atomics for those, it will definitely be tricky to get it right if it's at all possible, but that's when you should avoid relaxed atomics if at all possible. In the latter case, you should use relaxed atomics, and it's not tricky because you already know ordering doesn't matter.
Example: relaxed atomics are trivial to reason about in single-writer situations that aren't signaling any kind of event. Like when a worker is just trying to report progress to the foreground UI with minimal overhead. In this example, the writer (which is on a background thread) simply loads its progress indicator, adds 1, then stores back the variable. There isn't even a need for an atomic RMW, let alone any ordering. The worst case is the user will see "100% done" a few milliseconds too early, which is a non-issue (and already bound to happen due to rounding etc. anyway). Using relaxed atomics doesn't introduce any complexity in such a situation; it's basically exactly what you'd expect from ordinary variables without write-tearing.
You run into problems if you try to depend on the "100% progress" indicator to mean "everything is finished" (so you can, say, clean up data structures), but at that point you clearly need ordering and shouldn't use a relaxed atomic to begin with; you'd want release semantics (or stronger), and those are trickier.
> My confusion is that I believed this was all well-understood by implementers and programmers decades ago.
I definitely agree with this part. I didn't think in 2023 I would read that programmers are shocked that relaxed atomics can be reordered. That's... their entire point. It's like being shocked that compilers propagate constants during optimization.