33

Are the "modify" operators like +=, |=, &= etc atomic?

I know ++ is atomic (if you perform x++; in two different threads "simultaneously", you will always end up with x increased by 2, as opposed to x=x+1 with optimization switched off.)

What I wonder is whether variable |= constant, and the likes are thread-safe or do I have to protect them with a mutex?

(...or is it CPU-dependent? In this case, how is it on ARM?)

SF.
  • 13,549
  • 14
  • 71
  • 107
  • 1
    Which ARM? Architecture v6 (ARM10) and later can provide atomic operations, if the compiler supports it or you roll your own assembly. Earlier architectures can't. – Mike Seymour Mar 18 '10 at 11:20
  • gcc has builtin atomic operations: http://gcc.gnu.org/onlinedocs/gcc-4.4.3/gcc/Atomic-Builtins.html#Atomic-Builtins ; please note: "Not all operations are supported by all target processors" – Christoph Mar 18 '10 at 11:41
  • there's Windows API for interlocked variable access: http://msdn.microsoft.com/en-us/library/ms684122%28v=VS.85%29.aspx – Janusz Lenar Mar 18 '10 at 12:31
  • 1
    Apart from the fact that the language does not guarantee it, x86-64 has a strong memory mode (loads acquire and stores release), and even there, e.g. `INC m64` as an RMW op is not guaranteed to be atomic, unless the `LOCK` prefix is used. – Arne Vogel Nov 16 '18 at 13:43

12 Answers12

76

You are wrong. There is no guarantee that ++ is atomic and neither is there for the compound assignment operators, or indeed for any C++ operation.

TheDarkKnight
  • 27,181
  • 6
  • 55
  • 85
  • 4
    Which means this is CPU-specific. On single-core architectures that allow `inc [address]`, this is definitely atomic. – SF. Mar 18 '10 at 10:15
  • 2
    At least not until C++0x: "This Clause describes components for fine-grained atomic access. This access is provided via operations on atomic objects." [29.1/1 in the n3035 draft]. –  Mar 18 '10 at 10:15
  • 27
    @SF No it isn't. It is compiler specific. Just because a CPU architecture has an instruction does not mean that the compiler will use it in the way you think it should, if indeed it uses it at all. –  Mar 18 '10 at 10:16
  • 3
    Even if the compiler can generate an atomic increment, whether x++ can be atomic may depend on the data type of x. For example on SF's target an increment of x if x is 'long long', will not be atomic, and if it were another integral type, it may conceivably depend on the specific ARM architecture and the data alignment. – Clifford Mar 18 '10 at 12:45
  • 2
    @SF: That's very specific. What if the compiler chooses not to use that instruction? What if/when you upgrade to a multicore system? What about architectures that do not have the `inc` instruction? *In general*, `++` is **not** atomic. You've just found one single narrow special case where it is not an issue – jalf Mar 18 '10 at 13:42
  • @SF.: I don't even think `inc [address]` is atomic on x86. – user541686 Sep 03 '13 at 06:22
9

x++ is often implemented in 3 instructions: Read X into a register, Increment it, and Write it back to memory.

Your thread may be pre-empted in between any of those.

Alex Budovski
  • 17,947
  • 6
  • 53
  • 58
6

For the change in value to be visible across cores, a += (for instance) would have to load the value, add the increment and then store it. This means that the operation will not be atomic.

To ensure atomicity you'd need to put appropriate locking around the operation.

Benno
  • 5,640
  • 2
  • 26
  • 31
3

No operator in C or C++ is guaranteed to be atomic. They might be on your platform, but you won't know for sure. Typically, the only operation that is atomic is the an instruction Test and Set, which is usually available on most modern CPUs in some form as the basis for implementing semaphores.

plinth
  • 48,267
  • 11
  • 78
  • 120
3

No, they're not atomic! If you need atomic operations on primitive types, and you're using linux, you can take a look here: http://gcc.gnu.org/onlinedocs/gcc-4.1.0/gcc/Atomic-Builtins.html and/or atomic.h...

2

++ might be atomic on your compiler/platform, but in the c++ specs it is not defined to be atomic.

If you want to make sure to modify a value in an atomic way, you should use the appropiate methods, like Interlocked* on windows.

Same for all the other routines. If you want atomic operations, you should use the appropiate calls, not the standard ones.

Sam
  • 28,421
  • 49
  • 167
  • 247
2

It's both compiler and CPU dependent. Some instruction sets provide atomic instructions for these (on machine-sized ints).

However, there's no guarantee that you compiler will use those instructions and won't optimize your code in a non-threadsafe way. You need to either write routine in assembly or use a compiler specific technique (such as instrinsics) that provides atomicity (or use a library that uses one of those techniques).


Specifically on ARM: The ORR/ADD/AND instructions take two operands and place the result in a register. Either operand can be the same register as the result register, so they can used as an atomic |=, +=, &=.

Of course, the result is put in a register and the first operand must also come from a register, so you'll have to make sure the register loads are done atomically.

JoeG
  • 12,994
  • 1
  • 38
  • 63
1

Not only are they not atomic, like all operations, but they can have very interesting results. For example, if the compiler sees that it writes to x, it is allowed to use x as a temporary variable, rather than using up registers or stack space. This means x may temporarily contain ANY value, not just values that make sense for x

http://software.intel.com/en-us/blogs/2013/01/06/benign-data-races-what-could-possibly-go-wrong

Cort Ammon
  • 10,221
  • 31
  • 45
1

A duplicate was directed here, and this needs an update. The “new” C11 language enables an atomic attribute which admits that:

_Atomic int a;
...
a += 3

can be compiled into an (atomic) unbounded loop. Thanks for the gift standards folks, I really wish you hadn’t.

1: in some architectures, atomic operations are only possible on memory which supports certain access protocols. ARMv7, MIPS for example turn the sequence into:

do {
    x = LoadLinked(a) + 3;
} while !StoreConditional(x, &a);

but LoadLinked/StoreConditional is undefined for some memory/cache types. Enjoy debugging that.

2: Related is false sharing which is an artifact of LoadLinked, StoreConditional operating upon cache lines (eg. 32, 64, 256 bytes) not sub-blocks. So: _Atomic int a[4]; might require 4* cache line size (thus 1024 bytes) to safely permit simultaneous atomic operations on a[n] and a[n+1], because 4 cpu’s could be fighing to update a[0..3], but never succeeding.

Hopefully the next standard will recognize the inherent failure of attribute decoration, and reinstate c89 as the rightful C standard.

mevets
  • 10,070
  • 1
  • 21
  • 33
0

It's worth mentioning that these operators can be overloaded, so there can certainly be no general guarantee that they are atomic for all classes.

Oddthinking
  • 24,359
  • 19
  • 83
  • 121
0

Even if ++ is an atomic operation, that does not imply that two threads doing ++x will result in x being exactly two higher. You have to synchronize the threads somehow, or otherwise they won't necessarily see each other's changes.

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
0

You have to protect your variable, with a mutex for instance

Nikko
  • 4,182
  • 1
  • 26
  • 44