A point that may be obvious but hasn't been mentioned in other answers: lock-free atomic operations are normally implemented by hardware instructions and therefore relatively efficient; but they're typically still a lot more expensive than their non-atomic equivalents, and not as well optimized. Acquiring a mutex means you can safely write non-atomic code, and that may compensate for the cost of the mutex.
Imagine, as a trivial example, you have a large array of counters that need to be incremented. Your code does this a lot, so performance is important, but usually not from more than one thread at a time, so contention is low. It's also not important that the counter values remain consistent with each other during the run of the program. However, it may occasionally be called concurrently, so it still has to be thread-safe.
Your options are:
// lock-free code
std::atomic<int> counters[1000000];
void increment_counters() {
for (int i = 0; i < 1000000; i++)
counters[i].fetch_add(1, std::memory_order_relaxed);
}
This needs one million expensive atomic read-modify-write instructions. Versus:
// locking code
int counters[1000000];
std::mutex counter_lock;
void increment_counters() {
std::scoped_lock lk(counter_lock);
for (int i = 0; i < 1000000; i++)
counters[i]++;
}
This uses plain ordinary memory operations, and may even be vectorized for more speed. The savings could well outweigh the cost of locking and unlocking the mutex (which is usually very cheap when there's no contention), and it could even outweigh the cost of occasionally needing to block in the rare instance that two threads call increment_counters()
concurrently.
The difference would be even more extreme if instead of a simple increment, we needed to apply some more complex transformation to the array elements; then the atomic version would need a compare-exchange loop on every element.
atomic<T>
, but not the title. Some of the answers ignore it too. – Poacheratomic<T>
. If you have any suggestions for improving the question, please tell. – Iettastd::atomic<T>
you're saying that you want lock-free if it's available for typeT
. If only non-lock-free is acceptable thenstd::atomic<T>
is, semantically, the wrong tool for the job. – Nevisatomic<T>
; and you accepted one. It might be too late now, but I'd explicitly say in the title that you're asking only aboutatomic<T>
. – Poacher