Atomic Operations
Member Functions for Atomic Types
-
store()- Atomically replace the object's value with its argument
-
load()- Atomically return the object's value
-
operator =() -
operator T()- Synonyms for
store()andload()
- Synonyms for
-
exchange()- Atomically replace the object's value with its argument
- Returns the previous value
Member Functions for Specializations
-
Atomic pointers support pointer arithmetic
- Increment and decrement operators
fetch_add()synonym for x++fetch_sub()synonym for x--- += and -= operators
-
Integer Specializations have these, plus
- Atomic bitwise logical operations &, | and ^
#include <atomic>
#include <iostream>
int main() {
std::atomic<int> x = 0;
std::cout << "After initialization: x = " << x << '\n';
// Atomic assignment to x
x = 2;
// Atomic assignment from x. y can be non-atomic
int y = x;
std::cout << "After assignment: x = " << x << " , y = " << y << '\n';
x.store(3);
std::cout << "After store: x = " << x.load() << '\n';
std::cout << "Exchange returns " << x.exchange(y) << '\n';
std::cout << "After exchange: x = " << x << " , y = " << y << '\n';
}
std::atomic_flag
-
std::atomic_flagis an atomic boolean type- Has less overhead than
std::atomic<bool>
- Has less overhead than
-
Only three operations
clear()sets flag to falsetest_and_set()sets flag to true and returns previous valueoperator =()
-
Must be initialized to false
atomic_flag lock = ATOMIC_FLAG_INIT;
Spin Lock
-
A spin lock is essentially an infinite loop
- It keeps "spinning" until a condition becomes true
-
An alternative to locking a mutex or using a condition variable
-
We can use
std::atomic_flagto implement a basic spin lock- The loop condition is the value of the flag
Spin Lock with std::atomic_flag
-
Each thread calls
test_and_set()in a loop -
If this returns true
- Some other thread has set the flag and is in the critical section
- Iterate again
-
If it returns false
- This thread has set the flag
- Exit the loop and proceed into the critical section
-
After the critical section, set the flag to false
- This allows another thread to execute in the critical section
Spin Lock
#include <atomic>
#include <iostream>
#include <thread>
#include <vector>
// Initialize flag to false
std::atomic_flag flag = ATOMIC_FLAG_INIT;
void task(int n) {
// Loop until we can set the flag
while (flag.test_and_set()) {
}
// Critical section
using namespace std::literals;
std::this_thread::sleep_for(50ms);
std::cout << "I'm a task with argument " << n << '\n';
// End of critical section
flag.clear();
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.push_back(std::thread(task, i));
}
for (auto &thr : threads) {
thr.join();
}
}
Pros and Cons of spin lock
-
A spinning thread remains active
- A mutex may block the thread
-
It can continue immediately when it "gets the lock"
- With a mutex, the thread may need to be reloaded or woken up
-
Processor intensive
- Only suitable for protecting very short critical sections
- And / or very low contention
- Performance can be heavily impacted if spinning threads interrupt each other
- Usually only used in operating systems and libraries
Hybrid Mutex
-
Often used to implement
std::mutex -
Start with spin lock with a time out
- If the thread sets the flag in time, enter the critical section
- If the thread cannot set the flag in time, use the normal mutex implementation
-
This gives better performance than the conventional implementation