Reputation: 1113
I just know that even the code int64_t_variable = 10
is not an atomic operation. for example
int64_t i = 0;
i = 100;
in another thread (say T2) it may read the value which is not 0
or 100
.
std::atomic<int64_t> i; i.store(100, std::memory_order_relaxed)
is atomic. so what magic does atomic use to make it happen based on Q1 is true?v = n
, how I can know if it is atomic or not? for example, if v is void *
, is it atomic or not?==================================
Update: In my question: when T2 read the i
, both 0 and 100 are ok for me. but any other result is not reasonable. This is the point. so I don't think cpu cache or compiler stuff can make this happen.
Upvotes: 1
Views: 178
Reputation: 180998
is the above true?
Yes. If you do not use synchronization (std::atmoic<>
, std::mutex
, ...) then any change you make in one thread can not be assumed to show up in other threads. It could even be the compiler optimizes something away because there is no way it can change in function. For example
bool flag = true;
void stop_stuff()
{
flag = false;
}
void do_stuff()
{
while (flag)
std::cout << "never stop";
}
since there is no synchronization for flag
, the compiler is free to assume it never changes optmize away even checking flag in the loop condition. If it does then no matter how many times you call stop_stuff
, do_stuff
will never end.
If you change flag to std::atomic<bool> flag
, then the compiler can no longer make such an assumption as you are telling it that this variable can change outside the scope of the function and it needs to be checked.
Do note that not providing synchronization when you have more than one thread with shared data and at least one of those threads writes to the shared data is called a data race and per the standard is undefined behavior. The above example is just one possible outcome of that undefined behavior.
std::atomic<int64_t> i; i.store(100, std::memory_order_relaxed)
is atomic. so what magic does atomic use to make it happen based on Q1 is true?
It either uses an atomic primitive your system provides, or it uses a locking mechanism like std::mutex
to guard the access.
I always think any operation which handles less than 64 bits is atomic (assume 64 bit cpu), looks I was wrong. so for v = n, how I can know if it is atomic or not? for example, if v is void *, is it atomic or not?
While this may be true on some systems, it is not true for the C++ memory model. The only things that are atomic are std::atomic<T>
.
Upvotes: 4
Reputation: 4463
Upvotes: 0
Reputation: 23711
Yes. Reasons include "it requires multiple instructions", "memory caching can mess with it" and "the compiler may do surprising things (aka UB) if you fail to mention that the variable might be changed elsewhere".
The above reasons need to all be treated, and std::atomic
gives the compiler/C++ library implementation the information to do so. The compiler won't be allowed to do any surprise optimizations, it might issue cache flushes where necessary, and the implementation may use a locking mechanism to prevent different operations on the same object from interleaving. Note that not all of these may be necessary: x86 has some atomicity guarantees built in. You can inform yourself whether a given std::atomic
type is always lock-free on your platform with std::atomic::is_always_lockfree
, and whether a given instance is lock-free (due to e.g. aligned/unaligned access).
If you don't use std::atomic
, you have no atomicity guarantees due to the above reasons. It might be atomic on the instruction-level on your CPU, but C++ is defined on an abstract machine without such guarantees. If your code relies on atomicity on the abstract machine but fails to specify this, the compiler might make invalid optimizations and produce UB.
Upvotes: 1