I'm building the code below using Visual Studio 2015 Preview (but I've had a similar issue with Visual Studio 2013, so it's not specific to 2015), and then running the program under Intel Inspector XE 2013 Update 9 (build 328075). Intel Inspector reports several data races in code, around the access to shared_ptr and its atomic contents. Intel Inspector is known to report false positive races, I just want to confirm that is the case indeed, and understand whether this is an issue in the Inspector or in the Microsoft STL implementation...
Here is the code, there are 2 threads, which concurrently and atomically modify a shared_ptr pointing to an atomic int, and one of the thread also prints dots (so you can observe the activity printed on console):
#include <memory>
#include <atomic>
#include <thread>
#include <iostream>
using namespace std;
int main()
{
shared_ptr<atomic<int>> a;
atomic_store(&a, make_shared<atomic<int>>(42));
thread t1([&]
{
for (int i = 0; i < 1000000; i++)
{
auto ptr_copy = atomic_load(&a);
if (*ptr_copy == 42)
*ptr_copy = 0;
}
});
thread t2([&]
{
for (int i = 0; i < 1000000; i++)
{
auto ptr_copy = atomic_load(&a);
if (*ptr_copy == 0)
{
atomic_store(&a, make_shared<atomic<int>>(42));
cout << ".";
}
}
});
t1.join();
t2.join();
}
This is one of the data races reported, excerpt:
P1: Error: Data race
Error X22: Write: Function operator()
Code snippet:
28 if (*ptr_copy == 0)
29 {
>30 atomic_store(&a, make_shared<atomic<int>>(42));
31 cout << ".";
32 }
Error X23: Read: Function atomic_load_explicit<struct std::atomic<int> >
Code snippet:
1919 { // load *_Ptr atomically
1920 _Shared_ptr_spin_lock _Lock;
>1921 shared_ptr<_Ty> _Result = *_Ptr;
1922 return (_Result);
1923 }
Stack (1 of 1 instance(s))
>AtomicSharedPtrRace1.exe!atomic_load_explicit<struct std::atomic<int> > - c:\program files (x86)\microsoft visual studio 14.0\vc\include\memory:1921
AtomicSharedPtrRace1.exe!atomic_load<struct std::atomic<int> > - c:\program files (x86)\microsoft visual studio 14.0\vc\include\memory:1928
AtomicSharedPtrRace1.exe!operator() - atomicsharedptrrace1.cpp:17
I suppose Intel Inspector does not recognize the shared_ptr internal spinlock as a synchronization mechanism...
Questions:
- Is the sample code above data-race-free according to the standard?
If so, is the MS STL implementation of atomic_store/atomic_load standard-compliant? It ignores the memory order argument completely and seems to use a global spin lock, does it mean that it always enforces sequential consistency for memory operations and work as compiler barrier (even if I pass memory_order relaxed)?
template <class _Ty> inline shared_ptr<_Ty> atomic_load_explicit(const shared_ptr<_Ty> *_Ptr, memory_order) { // load *_Ptr atomically _Shared_ptr_spin_lock _Lock; shared_ptr<_Ty> _Result = *_Ptr; return (_Result); }
Is this a problem with Microsoft's implementation, that the spinlock is not recognized by Intel Inspector, or is it a problem with Intel Inspector? I would like to find some workaround, I suspect that many other data races reported by Inspector in our code could be false positives as they are around the place where we use atomic_load and atomic_store. I know that atomic_load and atomic_store will likely be deprecated in future, but I'd like to sort out those false positives somehow until then...