Trying to simulate data race for code:
struct Foo
{
// ~ 3100ms
alignas(std::hardware_destructive_interference_size) std::atomic<int> Counter1 = 0;
alignas(std::hardware_destructive_interference_size) std::atomic<int> Counter2 = 0;
// ~ 5900ms
//std::atomic<int> Counter1 = 0;
//std::atomic<int> Counter2 = 0;
// ~ 130ms
//alignas(std::hardware_destructive_interference_size) int Counter1 = 0;
//alignas(std::hardware_destructive_interference_size) int Counter2 = 0;
// ~ 270ms
//int Counter1 = 0;
//int Counter2 = 0;
};
int main()
{
Foo FooObj;
const int IncrementsByThread = 100000000;
std::thread T1([&FooObj, IncrementsByThread](){
for (int i = 0; i < IncrementsByThread; i++)
FooObj.Counter1++;
});
std::thread T2([&FooObj, IncrementsByThread](){
for (int i = 0; i < IncrementsByThread; i++)
FooObj.Counter2++;
});
T1.join();
T2.join();
std::cout << "Counters are " << FooObj.Counter1 << ", " << FooObj.Counter2 << std::endl;
return 0;
}
Result always the same, counters are equal, there are no data races. But false sharing exists without aligned data.
Not aligned Counter1 and Counter2 placed next to each other in memory, so cache line holds both of them. Have false sharing. After alignas() false sharing fixed.
But I thought there would be data race in case simple int Counters without alignas(). Looks like we have cache coherence working at all 4 cases with atomics, simple int, alignas()?
When do cache coherence protocols works? Thought only in cases atomic operations
Tryied to use atomics, alignas() to avoid false sharing. One int Counter makes data races, it is obviously, but I was thinking that simple non-atomic counters nearly placed in memory holding 1 cache line can produce data race situation and will be Counter1 != Counter2.