Reputation: 945
I have written this piece of code as test:
#include <iostream>
#include <thread>
#include <mutex>
int counter = 0;
auto inc(int a) {
for (int k = 0; k < a; ++k)
++counter;
}
int main() {
auto a = std::thread{ inc, 100000 };
auto b = std::thread{ inc, 100000 };
a.join();
b.join();
std::cout << counter;
return 0;
}
The counter
variable is global and so, creating 2 threads a
and b
, I'd expect to find a data race. The output is 200000 and not a random number. Why?
This code is a fixed version that uses a mutex
so that the global variable can be accessed only once (1 thread per time). The result is still 200000 .
std::mutex mutex;
auto inc(int a) {
mutex.lock();
for (int k = 0; k < a; ++k)
++counter;
mutex.unlock();
}
The fact is this. The mutex-solution gives me 200000 which is correct because only 1 threat at time can access counter. But why the non-mutex-solution still shows 200000?
Upvotes: 0
Views: 807
Reputation: 126777
The problem here is that your data race is extremely small. Any modern compiler will convert your inc
function to counter += a
, so the race window is extremely small - I'd even say that most probably once you start the second thread the first one has already finished.
This doesn't make this any less undefined behavior, but explains the result you are seeing. You may make the compiler less smart about your loop e.g. by making a
or k
or counter
volatile
; then your data race should become evident.
Upvotes: 6
Reputation: 20396
You can't make assertions about what should happen when a data race is involved. Your assertion that there should be some visible evidence of data tearing (i.e. the final result is 178592 or something) is false, because there's no reason to expect any such result.
The following code
auto inc(int a) {
for (int k = 0; k < a; ++k)
++counter;
}
Can be optimized legally according to the C++ standard into
auto inc(int a) {
counter += a;
}
Note how the number of writes into counter
has been optimized from O(a)
to O(1)
. That's pretty significant. What this means is that it's possible (and probable) that the write to counter
is finishing before the second thread has even been initialized, making the observation of data tearing statistically improbable.
If you want to force this code to behave the way you expect, consider marking the variable counter
as volatile
:
#include <iostream>
#include <thread>
#include <mutex>
volatile int counter = 0;
auto inc(int a) {
for (int k = 0; k < a; ++k)
++counter;
}
int main() {
auto a = std::thread{ inc, 100000 };
auto b = std::thread{ inc, 100000 };
a.join();
b.join();
std::cout << counter;
return 0;
}
Bear in mind that this is still undefined behavior, and should not be relied upon in any kind of production-destined code! However, this code is more likely to replicate the race condition you're trying to invoke.
You might also try larger numbers than 100000, since on modern hardware, even without optimizations, a loop of 100000 can be pretty fast.
Upvotes: 2
Reputation: 708
Data races are undefined behavior, which means that any program execution is valid, including program execution that happens to do what you want. In this case, the compiler is probably optimizing your loop into counter += a
and the first thread finishes before the second thread starts so they never actually conflict.
Upvotes: 3