Integer Operations and Threads
-
Integer operations are usually a single instruction
- True on x64
- Provided the data is correctly aligned and fits into a single word
-
A thread cannot be interrupted while performing integer operations
-
Do we still need to lock a shared integer?
#include <thread>
#include <iostream>
#include <vector>
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';
}
Integer Operation Discussion
-
The output is a number like 258413
- Data race
-
The ++ operation is a single instruction
-
However, ++count involves three operations
- Pre-fetch the value of the count
- Increment the value in the processor core's register
- Publish the new value of count
-
The thread could use a stale value in its calculation
-
The thread could publish its result after a thread which ran later
Thread Synchronization
-
We need to make sure that
- Thread B uses the latest value for count
- Thread A published its result immediately
-
A mutex does this internally when we call lock() and unlock()
-
Here, we can do this by declaring count as "atomic"
Atomic Keyword
-
The compiler will generate special instructions which
- Disable pre-fetch for count
- Flush the store buffer immediately after doing the increment
-
This also avoids some other problems
- Hardware optimizations which change the instruction order
- Compiler optimizations which change the instruction order
-
The result is that only one thread can access count at a time
-
This prevents the data race
- It also makes the operation take much longer