6 ms·
> It can be helpful to know that, on all modern processors, you can safety assign a value to a int, double, float, bool, BOOL or pointer variable on one thread,
by NieDzejkob 7y ago
> It can be helpful to know that, on all modern processors, you can safety assign a value to a int, double, float, bool, BOOL or pointer variable on one thread, and read it on a different thread without worrying about tearing, where only a portion of the value has been assigned by the time you read it.
What? NO! This is only relevant when you're writing assembly. In C, you have to use atomic (or possibly volatile, I'm not sure), or this just undefined behavior.
- coryrc 7y agoIf you are assigning something that fits in a single register, how would you get tearing? (Probably shouldn't have double in that list, in case it's in the special FPU registers)
- slx26 7y agoyeah, 64-bit types might cause problems (I found Rust's atomic module reference educative https://doc.rust-lang.org/std/sync/atomic/ https://doc.rust-lang.org/std/sync/atomic/ (the source code doesn't do anything too special either)), but the post said "modern processors", so I guess it's rather accurate
- cesarb 7y agoThe compiler is smarter than you think. Suppose one thread alternates between writing 32 and 33 to a 4-byte variable, while another thread alternates between writing 1024 and 1025 to the same 4-byte variable. You might think that the only possible values are 32, 33, 1024, 1025; but then you read the variable and find a 1056 or 1057 there. Why? Because the compiler noticed that the first thread, after its initial write of 32 or 33, only modifies the least significant byte of the value; so it generated a single-byte write to overwrite only the modified part of the value.
- joosters 7y agoI’d love to see a godbolt example of this optimisation, I’ve never heard of a compiler doing this sort of thing.
- vnorilo 7y agoThat one would be more of a pessimisation, I think.
- meheleventyone 7y agoWhich compilers exhibit this behaviour?
- PeCaN 7y agoIt's possible, if unlikely, that a compiler targeting the DEC Alpha could exhibit something similar (but with 16-bit values instead of bytes). See my reply to reitzensteinm. (I have no idea if any Alpha compilers did this in the wild.)
- meheleventyone 7y agoThat’s pretty fascinating, thank you!
- reitzensteinm 7y agoThere's zero benefit to just writing one byte. I'm having a hard time believing that a compiler capable of such analysis would use it for this purpose.
- nitrogen 7y agoI think the point is that unless the language guarantees atomicity (and the compiler implements the guarantee correctly), counterintuitive behavior is permissible and somewhat common, and compiler behavior varies across and within CPU architectures.
- reitzensteinm 7y agoActually I agree with the general point, OP is right that you should use volatile. I was just a bit taken back by the specific example above. However, as PeCaN has pointed out, I'm probably suffering from tunnel vision from x86 monoculture.
- PeCaN 7y agoFor the record I think the specific example from the OP is rather silly and of course never an optimization on x86(_64), but I'd never pass up a chance to talk about more fun architectures ;-)
- lalaland1125 7y agoFor reference, volatile is still the wrong choice for thread safety. If you need your race condition semantics to be well defined, you must use atomics (do note that those atomics might compile down to simple assignments if the platform supports that, but only the compiler is allowed to do that).
- vnorilo 7y agoVolatile is, well, volatile. You can basically only rely on the fact that the compiler respects source order and keeps all reads and writes (instead of hoisting and reordering). I believe msvc used to put in memory barriers. Not sure if it still does. These days you are better off ignoring the entire keyword and using the properly specified atomic stuff.
- de_watcher 7y agoCompiler may just reorder or optimize out some assignments. Caches may not sync between processors.
- vvanders 7y agoWhile he's technically right, you have no guarantee that the value as a whole is written unless you use the proper fences or synchronize it in some way.
- iainmerrick 7y ago“While he's technically right, you have no guarantee” Well, is he technically right or not? It sounds like you think he is not right.
- rocqua 7y agoHe's right on assembly level of assignment, but the compiler is free to emit assembly that will tear. Some compilers might do that for optimization purposes.
- Sharlin 7y agoThe (C or C++) compiler is free to do anything because reading and writing a non-`atomic` variable from two different threads without synchronization is undefined behavior.
- vvanders 7y agoHe's right that the value will be moved atomically, however the compiler is free to reorder the store/load of fences are omitted.
- jjoonathan 7y ago> In C, you have to use atomic (or possibly volatile, I'm not sure), or this just undefined behavior. Oh, if you think that's good, look at the compiler license: > WITHOUT WARRANTY OF ANY KIND, ..., INCLUDING, ... FITNESS FOR A PARTICULAR PURPOSE I guess that means we have to seriously consider the possibility that the compiler just ignores our code and outputs fork bombs instead. It would be completely irrational to ever depend on a thing that an official-sounding source declined to make promises about! /s
- jayd16 7y agoWouldn't the undefined behavior be whatever the CPU happens to do? Or do you think some optimization would introduce tearing?
- vnorilo 7y agoI wish people would not say "undefined behavior" to mean something that looks vaguely iffy or dangerous. It has a pretty specific meaning. [1] x84/64 has a pretty strong memory model. Bareback sharing of 64-bit variables has pretty much acquire-release semantics. Of course you should still say atomic and acq_rel when you actually mean that, but it's a question of portable code, not UB. Volatile stops the compiler from shuffling accesses around so that has can affect inter-thread observation order in some cases. [1]: http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html?m=1 http://blog.llvm.org/2011/05/what-every-c-programmer-should-...
- lalaland1125 7y agoRace conditions are undefined behavior though. It's even included in that post you just linked as an example of undefined behavior. > There are many other kinds of course, including sequence point violations like "foo(i, ++i)", race conditions in multithreaded programs, violating 'restrict', divide by zero, etc. (And no, volatile does not save you.)
- gpderetta 7y ago> Race conditions these are more precisely called data races. And yes, they are UB in C/C++.