The following simple code C++ example can be used for investigation of how GCC thread sanitizer works:
#include <mutex> #include <atomic> #include <iostream> #include <thread> std::mutex mutex; int a = 3; const size_t size = 1000 * 1000; std::atomic<int> b(1); void testA() { for (size_t counter = 0; counter < size; counter++) { ++b; std::unique_lock<std::mutex> lock(mutex); ++a; } } void testB() { for (size_t counter = 0; counter < size; counter++) { --b; std::unique_lock<std::mutex> lock(mutex); --a; } } int main() { std::thread t1(testA); std::thread t2(testB); t1.join(); t2.join(); }
The code is correct, but commenting out a mutex lock, for example, produces GCC thread sanitizer errors.
The code can be compiled with the following command, assuming the file name is race.cpp:
g++-7 -std=c++11 -pthread -fsanitize=thread -fno-omit-frame-pointer -fuse-ld=gold race.cpp -o race
The following code demonstrates the proper use of std::shared_ptr. Removing std::atomic_load or std::atomic_store causes data race.
#include <memory> #include <atomic> #include <iostream> #include <thread> struct A { A(int a) : x(a) { } int x = 0; }; std::shared_ptr<A> pA1; std::shared_ptr<A> pA2; std::shared_ptr<A> getA1() { return std::atomic_load(&pA1); } std::shared_ptr<A> getA2() { return std::atomic_load(&pA2); } std::shared_ptr<A> p_a; std::shared_ptr<A> getA() { return std::atomic_load(&p_a); } std::atomic<A *> p_naked(nullptr); const size_t size = 1000 * 1000; void test1() { std::atomic_store(&pA1, std::make_shared<A>(1)); for (size_t counter = 0; counter < size; counter++) { std::atomic_store(&p_a, getA2()); p_naked = getA().get(); } } void test2() { std::atomic_store(&pA2, std::make_shared<A>(2)); for (size_t counter = 0; counter < size; counter++) { std::atomic_store(&p_a, getA1()); p_naked = getA().get(); } } int main() { pA1 = std::make_shared<A>(3); pA2 = std::make_shared<A>(4); p_a = pA1; std::thread t1(test1); std::thread t2(test2); t1.join(); t2.join(); std::cout << "The value of p_a is: " << p_a->x << std::endl; }
Another example with QSettings that has its own mutex in get/set method:
#include <QSettings> #include <mutex> #include <atomic> #include <iostream> #include <thread> static const char * serviceModeKey = "app/serviceMode"; static const char * serviceModeKey2 = "app2/serviceMode"; QSettings qSettings("file.ini", QSettings::IniFormat); const size_t size = 10 * 1000 * 1000; void testA() { for (size_t counter = 0; counter < size; counter++) { qSettings.value(serviceModeKey, false).value<bool>(); qSettings.setValue(serviceModeKey2, counter % 2 == 0); } } void testB() { for (size_t counter = 0; counter < size; counter++) { qSettings.value(serviceModeKey2, false).value<bool>(); qSettings.setValue(serviceModeKey, counter % 2 == 0); } } int main() { std::thread t1(testA); std::thread t2(testB); t1.join(); t2.join(); }