Atomic Types
-
All operations on the variable will be atomic
-
C++11 defines an atomic template
- In the
<atomic>header - The parameter is the type of the object
- The object must initialized
// Atomic int, initialized to 0 atomic<int> x = 0; - In the
-
The parameter must be a type which is "trivially copyable"
- Scalar type
- Class where all the copy and move constructors are trivial
-
Normally only integer types and pointers are used
-
For more complex types, locks may be silently added
- Locking a mutex takes longer
- To avoid this, use a pointer to the type
Using an std::atomic<T> Object
- We can assign to and from the object
x = 2; // Atomic assignment to x
y = x; // Atomic assignment from x. y can be non-atomic
-
These are two distinct atomic operations
- Other threads could interleave between them
-
Operations such as ++ are atomic
- Fetch old value
- Increment
- Store new value
#include <thread>
#include <iostream>
#include <vector>
#include <atomic>
std::atomic<int> counter = 0;
void task() {
for (int i = 0; i < 100'000; ++i) {
++counter;
}
}
int main() {
std::vector<std::thread> tasks;
for (int i = 0; i < 10; ++i) {
tasks.push_back(std::thread(task));
}
for (auto &thr : tasks)
thr.join();
std::cout << counter << '\n';
}
Volatile Keyword
-
May change without explicit modification
- Prevents some compiler optimizations
- Typically used when accessing hardware
-
Often mistakenly used in threading
- Some programmers expect the Java / C# behaviour
- Has no impact on thread safety
-
At one point, Visual Studio supported this in C++
- Removed before C++11
Double-checked Locking
- One solution is to make the initialized object atomic
atomic<Test *> ptest = nullptr; - Atomic types do not support the
.or->operators - We must copy to a non-atomic pointer before we can use it
Test *ptr = ptest;
ptr->func();