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

