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();
}

