An example of how GCC thread sanitizer works.

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

Leave a Reply

Your email address will not be published. Required fields are marked *