Reputation: 12371
In the question How to use std::atomic<>, obviously we can just use std::mutex
to keep thread safety. I want to know when to use which one.
struct A {
std::atomic<int> x{0};
void Add() {
x++;
}
void Sub() {
x--;
}
};
vs.
std::mutex mtx;
struct A {
int x = 0;
void Add() {
std::lock_guard<std::mutex> guard(mtx);
x++;
}
void Sub() {
std::lock_guard<std::mutex> guard(mtx);
x--;
}
};
Upvotes: 9
Views: 4921
Reputation: 13634
std::atomic
has methods is_lock_free
(non-static) and is_always_lock_free
(static).
When is_lock_free
returns true, it means atomic does not have locks, and expected to perform better than the equivalent code with locks. When is_lock_free
returns false, it means that atomic has a lock, and equivalent performance with code with locks.
This does not mean that you should always use atomic instead of mutex-based approach, conversely, if you expect is_lock_free
to be always false, you should not use atomic:
CPUs that support multithreading provide atomic instructions of CPU register size. Often they provide atomic instructions of double CPU register size.
Currently most programs are run in 64-bit mode on a 64-bit CPU, but some programs still run in a 32-bit mode or on 32-bit CPU.
STL implementation can do an smaller-size atomic type using a larger sized CPU instructions, if there are no exact-sized instructions.
There are restricted set of operations that can be done on arbitrary type as underlying atomic type, there are more ops for integers (and floats).
Together it means that a type of 64-bit size or less is likely to be a good candidate for atomic, provided that it is either a native integer/floating type, or a small structure that is updated as a whole with a new version.
You should be looking into a bigger picture.
Acquiring a mutex in ideal (and normally frequent) scenario is nothing more than an atomic operation, ditto releasing a mutex.
On the other hand, atomic operations are often an order of magnitude slower than non-atomic equivalent. E.g. i++
will be way slower for atomic<int>
than for int
.
So, despite updating one counter using atomic is likely to be good, doing something more using atomics, like updating five counters, may be more complex and slower than using a single mutex to protect the whole operation.
Upvotes: 1
Reputation: 234635
As a rule of thumb, use std::atomic
for POD types where the underlying specialisation will be able to use something clever like a bus lock on the CPU (which will give you no more overhead than a pipeline dump), or even a spin lock. On some systems, an int
might already be atomic, so std::atomic<int>
will specialise out effectively to an int
.
Use std::mutex
for non-POD types, bearing in mind that acquiring a mutex is at least an order of magnitude slower than a bus lock.
If you're still unsure, measure the performance.
Upvotes: 10