Isn't atomic<bool>
redundant because bool
is atomic by nature? I don't think it's possible to have a partially modified bool value. When do I really need to use atomic<bool>
instead of bool
?
No type in C++ is "atomic by nature" unless it is an std::atomic*
-something. That's because the standard says so.
In practice, the actual hardware instructions that are emitted to manipulate an std::atomic<bool>
may (or may not) be the same as those for an ordinary bool
, but being atomic is a larger concept with wider ramifications (e.g. restrictions on compiler re-ordering). Furthermore, some operations (like negation) are overloaded on the atomic operation to create a distinctly different instruction on the hardware than the native, non-atomic read-modify-write sequence of a non-atomic variable.
std::atomic_flag
is the only exception, although its name start with atomic also. –
Dercy std::atomic*
and not std::atomic<*>
. –
Corrade Remember about memory barriers. Although it may be impossible to change bool
partially, it is possible that multiprocessor system has this variable in multiple copies and one thread can see old value even after another thread has changed it to new. Atomic introduces memory barrier, so it becomes impossible.
volatile
fix the multiprocessor issue? –
Belaud volatile
in Java. The volatile
keyword in Java does control memory fences but has a very different behavior than the volatile
keyword in C which does not. This question explains the difference further. –
Hydracid std::memory_order_relaxed
to get atomicity without ordering. This answer is totally wrong: barriers don't create atomicity because for example they don't stop a store from another thread from appearing between tmp=var; tmp++; var=tmp;
. Special CPU instructions are needed to make that sequence into an atomic RMW. See also Can num++ be atomic for 'int num'? –
Remscheid atomic
defaults to sequential-consistency ordering, say that. That's not required for atomicity, and conflicting values in cache for the same variable aren't possible: MESI cache coherence prevents that. atomic
implies some of the same things as volatile
so the compiler doesn't hoist the variable's value into a register, though. That isn't coherent. –
Remscheid atomic<T>
really does on normal systems is stop the compiler from keeping the value in a thread-private register –
Remscheid C++'s atomic types deal with three potential problems. First, a read or write can be torn by a task switch if the operation requires more than one bus operation (and that can happen to a bool
, depending on how it's implemented). Second, a read or write may affect only the cache associated with the processor that's doing the operation, and other processors may have a different value in their cache. Third, the compiler can rearrange the order of operations if they don't affect the result (the constraints are a bit more complicated, but that's sufficient for now).
You can deal with each of these three problems on your own by making assumptions about how the types you are using are implemented, by explicitly flushing caches, and by using compiler-specific options to prevent reordering (and, no, volatile
doesn't do this unless your compiler documentation says it does).
But why go through all that? atomic
takes care of it for you, and probably does a better job than you can do on your own.
while(!var) {}
into if(!var) infloop();
. This part of atomic
is similar to what volatile
does: always re-read from memory (which is cached but coherent). –
Remscheid volatile
+ barriers. And you'd need inline asm for RMW atomics like var += 1;
to be a single atomic increment instead of an atomic load, increment inside the CPU, then a separate atomic store. –
Remscheid atomic
, and that there are only 3 of them. ISO C++ doesn't even mention caches; that's on you so I think it's fair to criticise your choice of what to talk about as far as caches. You're not technically wrong, just IMO misleading. –
Remscheid atomic<bool>
will take care of for you "if it's an issue on the target system". Even though it isn't on any C++ implementation I'm aware of, if the reader didn't know that then they definitely aren't ready to roll their own atomics on top of volatile
or compiler-specific memory barriers. –
Remscheid std::atomic<bool>
gives you at least 2 other things you didn't mention: well-defined behaviour if another thread changes a value you're reading in a loop. (So it has that in common with volatile
: force a re-read from memory). And make read-modify-write operations like b ^= 1;
atomic. Except atomic<bool>
doesn't have a negate function, but there is b.compare_exchange_weak
or .exchange
which are atomic. e.g. on x86 you get lock cmpxchg
instead of just load/branch or whatever. How to atomically negate an std::atomic_bool? –
Remscheid volatile
does not give you well-defined behaviour for this in ISO C++. Only on specific implementations (like GNU C for a known set of ISAs) can you usefully roll your own atomics on top of volatile
, ignoring the fact that it's technically UB, like the Linux kernel. I should have said and instead of or implementation-defined stuff. I think in practice you'd be hard-pressed to find an implementation where volatile
would break for this; like I said I don't think there are any C++ implementations on non-coherent shared memory hardware, and that's highly non-standard. –
Remscheid atomic
not volatile
. My claim is that atomic
doesn't give you the behavior that one thread reading a variable will see the new value after another thread writes it, in theory. In practice it does, as a QoI issue and because the optimization that would break this is somewhat unlikely. –
Orel if(!b) infloop();
to be equivalent according to the as-if rule to while(!b){}
, you'd have to decide that all infinity of the reads of b
are contiguous in the global order of reads and writes for b
. i.e. that they all happen-before any possible write from another thread. I guess that's possible in theory for a DeathStation 9000 implementation, but very obviously isn't the intent of the standard. It might not even be standard-compliant depending on the order of the program starting its threads. –
Remscheid atomic<>
in the ISO standard doesn't quite guarantee cache flushing on a non-coherent system. –
Remscheid Consider a compare and exchange operation:
bool a = ...;
bool b = ...;
if (a)
swap(a,b);
After we read a, we get true, another thread could come along and set a false, we then swap (a,b), so after exit b is false, even though the swap was made.
Using std::atomic::compare_exchange
we can do the entire if/swap logic atomically such that the other thread could not set a to false in between the if and the swap (without locking). In such a circumstance if the swap was made than b must be false on exit.
This is just one example of an atomic operation that applies to a two value type such as bool.
Atomic operations are about more than just torn values, so while I agree with you and other posters that I am not aware of an environment where torn bool
is a possibility, there is more at stake.
Herb Sutter gave a great talk about this which you can view online. Be warned, it is a long and involved talk. Herb Sutter, Atomic Weapons. The issue boils down to avoiding data races because it allows you to have the illusion of sequential consistency.
Atomicity of certain types depends exclusively on the underlying hardware. Each processor architecture has different guarantees about atomicity of certain operations. For example:
The Intel486 processor (and newer processors since) guarantees that the following basic memory operations will always be carried out atomically:
- Reading or writing a byte
- Reading or writing a word aligned on a 16-bit boundary
- Reading or writing a doubleword aligned on a 32-bit boundary
Other architectures have different specifications on which operations are atomic.
C++ is a high-level programming language that strives to abstract you from the underlying hardware. For this reason standard simply cannot permit one to rely on such low-level assumptions because otherwise your application wouldn't be portable. Accordingly, all the primitive types in C++ are provided with atomic
counterparts by C++11 compliant standard library out-of-the-box.
atomic
sort of includes this property of volatile
, so while(!var){}
can't optimize into if(!var) infinite_loop();
. See MCU programming - C++ O2 optimization breaks while loop –
Remscheid © 2022 - 2024 — McMap. All rights reserved.
atomic<bool>
to avoid race-conditions. A race-condition occurs if two threads access the same memory location, and at least one of them is a write operation. If your program contains race-conditions, the behavior is undefined. – Peonageint
value where you are copying each byte or word of that value individually. There therefore shouldn't be any race condition, if the write is already atomic. – Maddy