Are C++ Reads and Writes of an int Atomic? [duplicate]
Asked Answered
S

16

97

I have two threads, one updating an int and one reading it. This is a statistic value where the order of the reads and writes is irrelevant.

My question is, do I need to synchronize access to this multi-byte value anyway? Or, put another way, can part of the write be complete and get interrupted, and then the read happen.

For example, think of a value = 0x0000FFFF that gets incremented value of 0x00010000.

Is there a time where the value looks like 0x0001FFFF that I should be worried about? Certainly the larger the type, the more possible something like this to happen.

I've always synchronized these types of accesses, but was curious what the community thinks.

Samaritan answered 10/9, 2008 at 14:26 Comment(6)
Really? I wouldn't care what the community thought. I would care what the facts are :)Prowler
Interesting read on the topic: channel9.msdn.com/Shows/Going+Deep/…Peaked
Specifically for =: #8291268Fenderson
Above Video link is dead. But it is on youtubeFetation
@JanSchultke - thanks for the cleanup work on these old [atomic] questions. Another relevant Q&A for pure-load / pure-store is Why is integer assignment on a naturally aligned variable atomic on x86? - my answer explains exactly what's guaranteed atomic in x86 asm, but the top of my answer explains that you need std::atomic<int> (perhaps with memory_order_relaxed) to get the compiler to make the asm you want. That part is true on all ISAs.Piccaninny
@Jan: Also a more recent Q&A is Which types on a 64-bit computer are naturally atomic in gnu C and gnu C++? -- meaning they have atomic reads, and atomic writes is relevant, debunking the idea that any plain type can be "naturally" atomic in C++, and showing a practical example of GCC for AArch64 choosing to do a not-guaranteed-atomic store of a plain int64_t (unlike with atomic<> or volatile) for a simple assignment. That's probably a better choice as a duplicate target for questions about whether int "is atomic", since it's not just 64-bit typesPiccaninny
R
48

At first one might think that reads and writes of the native machine size are atomic but there are a number of issues to deal with including cache coherency between processors/cores. Use atomic operations like Interlocked* on Windows and the equivalent on Linux. C++0x will have an "atomic" template to wrap these in a nice and cross-platform interface. For now if you are using a platform abstraction layer it may provide these functions. ACE does, see the class template ACE_Atomic_Op.

Rieger answered 10/9, 2008 at 15:37 Comment(1)
The document of ACE_Atomic_Op has moved - it can now be found at dre.vanderbilt.edu/~schmidt/DOC_ROOT/ACE/ace/Atomic_Op.inlRebarebah
R
64

Boy, what a question. The answer to which is:

Yes, no, hmmm, well, it depends

It all comes down to the architecture of the system. On an IA32 a correctly aligned address will be an atomic operation. Unaligned writes might be atomic, it depends on the caching system in use. If the memory lies within a single L1 cache line then it is atomic, otherwise it's not. The width of the bus between the CPU and RAM can affect the atomic nature: a correctly aligned 16bit write on an 8086 was atomic whereas the same write on an 8088 wasn't because the 8088 only had an 8 bit bus whereas the 8086 had a 16 bit bus.

Also, if you're using C/C++ don't forget to mark the shared value as volatile, otherwise the optimiser will think the variable is never updated in one of your threads.

Rahr answered 10/9, 2008 at 14:42 Comment(5)
The volatile keyword is not useful in multithreaded programs #2485480Septicidal
@IngeHenriksen: I'm not convinced by that link.Rahr
another source, but unfortunately very old (it predates std::atomic): web.archive.org/web/20190219170904/https://software.intel.com/…Noaccount
This answer is outdated. As of C++11, unsynchronized access to an int that is not std::atomic<int> is a data race, and is undefined behavior. So the current correct answer is a flat no.Instancy
@NateEldredge: That's not entirely correct, undefined does not mean "a flat no". As I said in my answer, "it depends". Undefined just means there's no guarentee the operation is atomic, sometimes it is, sometimes it's not. The code is not going to be portable if you make assumptions about the atomic nature, but if you're targeting a fixed hardware and software configuration and comment the code appropriately then the assumption might work for you. But, like I said, it won;t be truely portable.Rahr
R
48

At first one might think that reads and writes of the native machine size are atomic but there are a number of issues to deal with including cache coherency between processors/cores. Use atomic operations like Interlocked* on Windows and the equivalent on Linux. C++0x will have an "atomic" template to wrap these in a nice and cross-platform interface. For now if you are using a platform abstraction layer it may provide these functions. ACE does, see the class template ACE_Atomic_Op.

Rieger answered 10/9, 2008 at 15:37 Comment(1)
The document of ACE_Atomic_Op has moved - it can now be found at dre.vanderbilt.edu/~schmidt/DOC_ROOT/ACE/ace/Atomic_Op.inlRebarebah
L
11

IF you're reading/writing 4-byte value AND it is DWORD-aligned in memory AND you're running on the I32 architecture, THEN reads and writes are atomic.

Leventis answered 10/9, 2008 at 14:41 Comment(4)
Where in the Intel architecture software developer's manuals is this stated?Hysterotomy
@DanielTrebbien: perhaps see #5002546Prowler
This is not true at the level of C++. The underlying machine instructions are atomic, but the compiler is allowed to optimize in ways that would break atomicity.Instancy
@DanielTrebbien Why is integer assignment on a naturally aligned variable atomic on x86? quotes the Intel and AMD manuals for asm guarantees, after explaining that you need to use std::atomic<int> to get the compiler to make asm that actually does memory operations that match what you're doing to the C++ objects, even for pure-load and pure-store, not just atomic RMW like some other answers here are talking about.Piccaninny
J
10

Yes, you need to synchronize accesses. In C++0x it will be a data race, and undefined behaviour. With POSIX threads it's already undefined behaviour.

In practice, you might get bad values if the data type is larger than the native word size. Also, another thread might never see the value written due to optimizations moving the read and/or write.

Juanitajuanne answered 10/9, 2008 at 14:31 Comment(0)
A
3

You must synchronize, but on certain architectures there are efficient ways to do it.

Best is to use subroutines (perhaps masked behind macros) so that you can conditionally replace implementations with platform-specific ones.

The Linux kernel already has some of this code.

Ammoniac answered 10/9, 2008 at 14:38 Comment(0)
H
3

On Windows, Interlocked***Exchange***Add is guaranteed to be atomic.

Hemline answered 16/9, 2008 at 21:46 Comment(0)
T
2

To echo what everyone said upstairs, the language pre-C++0x cannot guarantee anything about shared memory access from multiple threads. Any guarantees would be up to the compiler.

Troyes answered 11/9, 2008 at 16:7 Comment(0)
F
1

Some people think that ++c is atomic, but have a eye on the assembly generated. For example with 'gcc -S' :

movl    cpt.1586(%rip), %eax
addl    $1, %eax
movl    %eax, cpt.1586(%rip)

To increment an int, the compiler first load it into a register, and stores it back into the memory. This is not atomic.

Flavoprotein answered 28/3, 2012 at 7:39 Comment(1)
This is not an issue if only one thread is writing to the variable, since there's no tearing.Hammer
A
0

No, they aren't (or at least you can't assume they are). Having said that, there are some tricks to do this atomically, but they typically aren't portable (see Compare-and-swap).

Axiom answered 10/9, 2008 at 14:30 Comment(0)
D
0

I agree with many and especially Jason. On windows, one would likely use InterlockedAdd and its friends.

Delarosa answered 10/9, 2008 at 15:0 Comment(0)
B
0

Asside from the cache issue mentioned above...

If you port the code to a processor with a smaller register size it will not be atomic anymore.

IMO, threading issues are too thorny to risk it.

Brendabrendan answered 12/9, 2008 at 1:3 Comment(0)
L
0

Definitively NO ! That answer from our highest C++ authority, M. Boost:
Operations on "ordinary" variables are not guaranteed to be atomic.

Libelous answered 22/10, 2013 at 9:13 Comment(1)
that link only says arithmetic operation which consists of a read-update-write sequence on 'ordinary' variables are not atomic, not whether read or write operation on 'ordinary' variables are atomic or not.Contumelious
T
-1

The only portable way is to use the sig_atomic_t type defined in signal.h header for your compiler. In most C and C++ implementations, that is an int. Then declare your variable as "volatile sig_atomic_t."

Tapir answered 9/5, 2010 at 1:47 Comment(2)
volatile doesn't do what you think it does #2485480Fumed
sig_atomic_t is atomic with respect to signals, but not with respect to threads. Since C++11, accessing such an object from multiple threads is a data race and UB.Instancy
C
-1

Lets take this example

int x;
x++;
x=x+5;

The first statement is assumed to be atomic because it translates to a single INC assembly directive that takes a single CPU cycle. However, the second assignment requires several operations so it's clearly not an atomic operation.

Another e.g,

x=5;

Again, you have to disassemble the code to see what exactly happens here.

Chromate answered 31/7, 2010 at 16:39 Comment(2)
But the compiler could optimize it into x+=6.Emad
inc dword [rdi] isn't atomic except on a uniprocessor machine. You need a lock prefix to be SMP-safe. Is incrementing an int effectively atomic in specific cases?Piccaninny
C
-1

tc, I think the moment you use a constant ( like 6) , the instruction wouldn't be completed in one machine cycle. Try to see the instruction set of x+=6 as compared to x++

Chromate answered 19/8, 2010 at 9:22 Comment(2)
This looks like it was supposed to be a reply to a comment on your earlier answer. It's also wrong; on a uniprocessor machine where your original premise was right that a single instruction RMW is atomic even without the lock prefix, add dword [rdi], 6 is not different from inc dword [rdi]. Both can be made actually atomic with a lock prefixPiccaninny
Neither one can execute in a single cycle, though: the load, add, and store uops all depend on each other, and even an L1d cache hit has a few cycles of load latency. Out-of-order exec pipelines can run this and 3 or more other instructions in the same cycle, though, if they're independent (not incrementing the same memory location or register).Piccaninny
Y
-2

Reads and writes are atomic, but you also need to worry about the compiler re-ordering your code. Compiler optimizations may violate happens-before relationship of statements in your code. By using atomic you don't have to worry about that. ... atomic i;

soap_status = GOT_RESPONSE ; i = 1

In the above example, the variable 'i' will only be set to 1 after we get a soap response.

Yong answered 30/5, 2021 at 14:5 Comment(1)
This is not true. Reads and writes of int are not guaranteed atomic in standard C++, and the resulting data race causes undefined behavior.Instancy

© 2022 - 2024 — McMap. All rights reserved.