Data races in C++ multithreaded applications occur when multiple threads try to access a single data without proper data locking tools like mutex.
Causes of Data Races
- Concurrent Reads and Writes : If one thread reads a variable while another writes to it without synchronization, a data race occurs.
- Concurrent Writes : If multiple threads write to the same variable concurrently without synchronization, it leads to a data race.
- Lack of Synchronization Primitives : Not using mutexes, atomic operations, or other synchronization mechanisms can result in data races.
Data race may cause unpredictable output and even crashes.
For example, look at the below C++ programs.
Here two threads are trying to access a single variable without a proper synchronization technique.
Here is the video in which the program is run in Eclipse.
Code without synchronization...
//============================================================================
// Name : C++20Atomic.cpp
// Author : Som
// Version :
// Copyright : som-itsolutions
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
#include <atomic>
#include <thread>
int counter = 0;
void incrementCounter() {
for (int i = 0; i < 10000; ++i) {
counter++;
}
}
int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter << std::endl;
return 0;
}
You can see that as the counter increases, the output is incorrect. This is because we didn't use any thread-safe way to access the variable.
Code with Synchronization...
//============================================================================
// Name : C++20Atomic.cpp
// Author : Som
// Version :
// Copyright : som-itsolutions
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
#include <thread>
#include <atomic>
// Using std::atomic<int> to ensure thread-safe operations on //the counter
std::atomic<int> counter{0};
void incrementCounter() {
for (int i = 0; i < 10000; ++i) {
// The ++ operator for std::atomic is an atomic operation
counter++;
}
}
int main() {
std::thread t1(incrementCounter);
std::thread t2(incrementCounter);
t1.join();
t2.join();
std::cout << "Final counter value: " << counter.load() << std::endl;
return 0;
}
In C++ 20, with the introduction of atomic data, this can be easily handled.
So, now we have changed the simple variable into an atomic variable.
And voila...
The output result is perfect...
Watch the correct program running...
C++ is gradually becoming powerful after a very long period of hibernation.
Enjoy...
Top comments (0)