After learning about atomic operations, I have rarely used them, forgotten a lot, and still have many things I don’t understand. For example, a question that popped up recently:
std::atomic
In the process of assignment and comparison, are they atomic operations?
In order to verify the results, a demo is written here to verify a == 1
whether a = 1
the sum process is also an atomic operation.
#include <atomic>
using namespace std;
int main() {
atomic<int> a;
if(a == 1)
{
a = 2;
}
else if(a.load() == 3)
{
a.store(4);
}
a.load();
}
The assembly code is as follows:
main:
mov eax, DWORD PTR [rsp-4]
cmp eax, 1
je .L4
mov eax, DWORD PTR [rsp-4]
cmp eax, 3
je .L5
.L3:
mov eax, DWORD PTR [rsp-4]
mov eax, 0
ret
.L4:
mov eax, 2
xchg eax, DWORD PTR [rsp-4]
jmp .L3
.L5:
mov eax, 4
xchg eax, DWORD PTR [rsp-4]
jmp .L3
It's not very intuitive to look at it this way. You can check it out on the godbolt website.
https://godbolt.org/z/n4bs3e3Kn
It is concluded from the compilation results:
a == 1
a.load() == 2
is consistent witha = 2
anda.store(2)
Then, I checked the relevant information and learned from the book "C++ Standard Library" that store()
these load()
operations are guaranteed to be atomic (uncuttable). At the same time, it was also mentioned:
For atomic types, you can continue to use useful and common operations, such as assignment, automatic conversion to integer, increment, decrement, etc.
std::atomic<bool> ab(false); ab = true; if(ab){ ... } std::atomic<int> ai(0); int x = ai; ai = 10; ai++; ai-=17;
Well, here is further confirmation of the answer to the question.
Replenish
This passage needs to be savored carefully
store()
A so-called release operation will be performed on the affected memory area to ensure that all previous memory operations, whether atomic or not, become "visible to other threads" before the store takes effect.
load()
A so-called acquire operation will be performed on the affected memory area to ensure that all subsequent memory operations, whether atomic or not, become "visible to other threads" after load.
My understanding: "Affected memory area" refers to the memory area of this atomic variable and the associated memory area. "Can be seen by other threads" is equivalent to hiding the process when performing atomic operations, and other threads can see it. If it is missing, then other threads cannot make changes. After performing the atomic operation, it will be displayed again, so that other threads can see it and perform read and write operations.
In short, store()
the delete operation is performed on the memory area where it is located first, regardless of what the previous memory area looked like, because it has been cleared, and then the locked assignment operation is performed, and the lock is unlocked after the assignment is completed. load()
First, lock the memory area where it is located, and then unlock it after obtaining the value.
Points to note
When initializing an atomic variable with val assignment, it is not an atomic operation.
std::atomic<T> a = val;//这个不是原子操作
std::atomic<T> b(val);//原子操作
Increment and decrement are actually overloaded operations, calling sum fetch_add()
and fetch_sub()
returning the copied value, +=
sum -=
is equivalent to fetch_add()
andfetch_sub()