I wrote the following toy example:
std::map<char, size_t> getMap(const std::string& s)
{
std::map<char, size_t> map;
size_t i = 0;
for (const char * b = s.data(), *end = b + s.size(); b != end; ++b)
{
map[*b] = i++;
}
return map;
}
void check(const std::string& s)
{
//The creation of the map should be thread safe according to the C++11 rules.
static const auto map = getMap("12abcd12ef");
//Now we can read the map concurrently.
size_t n = 0;
for (const char* b = s.data(), *end = b + s.size(); b != end; ++b)
{
auto iter = map.find(*b);
if (iter != map.end())
{
n += iter->second;
}
}
std::cout << "check(" << s << ")=" << n << std::endl;
}
int main()
{
std::thread t1(check, "abc");
std::thread t2(check, "def");
t1.join();
t2.join();
return 0;
}
According to the C++11 standard, this should not contain any data race (cf. this post).
However TSAN with gcc 4.9.2, reports a data race:
==================
WARNING: ThreadSanitizer: data race (pid=14054)
Read of size 8 at 0x7f409f5a3690 by thread T2:
#0 TestServer::check(std::string const&) <null>:0 (TestServer+0x0000000cc30a)
#1 std::thread::_Impl<std::_Bind_simple<void (*(char const*))(std::string const&)> >::_M_run() <null>:0 (TestServer+0x0000000cce37)
#2 execute_native_thread_routine ../../../../../gcc-4.9.2/libstdc++-v3/src/c++11/thread.cc:84 (libstdc++.so.6+0x0000000b5bdf)
Previous write of size 8 at 0x7f409f5a3690 by thread T1:
#0 TestServer::getMap(std::string const&) <null>:0 (TestServer+0x0000000cc032)
#1 TestServer::check(std::string const&) <null>:0 (TestServer+0x0000000cc5dd)
#2 std::thread::_Impl<std::_Bind_simple<void (*(char const*))(std::string const&)> >::_M_run() <null>:0 (TestServer+0x0000000cce37)
#3 execute_native_thread_routine ../../../../../gcc-4.9.2/libstdc++-v3/src/c++11/thread.cc:84 (libstdc++.so.6+0x0000000b5bdf)
Location is global 'TestServer::check(std::string const&)::map' of size 48 at 0x7f409f5a3680 (TestServer+0x00000062b690)
Thread T2 (tid=14075, running) created by main thread at:
#0 pthread_create ../../../../gcc-4.9.2/libsanitizer/tsan/tsan_interceptors.cc:877 (libtsan.so.0+0x000000047c03)
#1 __gthread_create /home/Guillaume/Compile/objdir/x86_64-unknown-linux-gnu/libstdc++-v3/include/x86_64-unknown-linux-gnu/bits/gthr-default.h:662 (libstdc++.so.6+0x0000000b5d00)
#2 std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>) ../../../../../gcc-4.9.2/libstdc++-v3/src/c++11/thread.cc:142 (libstdc++.so.6+0x0000000b5d00)
#3 TestServer::main() <null>:0 (TestServer+0x0000000ae914)
#4 StarQube::runSuite(char const*, void (*)()) <null>:0 (TestServer+0x0000000ce328)
#5 main <null>:0 (TestServer+0x0000000ae8bd)
Thread T1 (tid=14074, finished) created by main thread at:
#0 pthread_create ../../../../gcc-4.9.2/libsanitizer/tsan/tsan_interceptors.cc:877 (libtsan.so.0+0x000000047c03)
#1 __gthread_create /home/Guillaume/Compile/objdir/x86_64-unknown-linux-gnu/libstdc++-v3/include/x86_64-unknown-linux-gnu/bits/gthr-default.h:662 (libstdc++.so.6+0x0000000b5d00)
#2 std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>) ../../../../../gcc-4.9.2/libstdc++-v3/src/c++11/thread.cc:142 (libstdc++.so.6+0x0000000b5d00)
#3 TestServer::main() <null>:0 (TestServer+0x0000000ae902)
#4 StarQube::runSuite(char const*, void (*)()) <null>:0 (TestServer+0x0000000ce328)
#5 main <null>:0 (TestServer+0x0000000ae8bd)
SUMMARY: ThreadSanitizer: data race ??:0 TestServer::check(std::string const&)
==================
What is wrong here ?
is TSan buggy ? (When I am using Clang's toolchain, I get no data race report) does GCC emit code which is not thread safe? (I am not using -fno-threadsafe->statics though) is my understanding of static locals incorrect?
I believe this is bug in gcc
part that generate code for tsan
purposes.
I try this:
#include <thread>
#include <iostream>
#include <string>
std::string message()
{
static std::string msg("hi");
return msg;
}
int main()
{
std::thread t1([]() { std::cout << message() << "\n"; });
std::thread t2([]() { std::cout << message() << "\n"; });
t1.join();
t2.join();
}
If look at code generate by clang
and gcc
, all good,
__cxa_guard_acquire
is called in both cases for path that init static local variable. But in case of check that we need init msg
or not we have problem.
The code looks like this
if (atomic_flag/*uint8_t*/) {
lock();
call_constructor_of_msg();
unlock();
}
in case of clang
callq __tsan_atomic8_load
was generated,
but in the case of gcc
it generate callq __tsan_read1
.
Note that this calls annotate real memory operations,
not do operations by itself.
so it at runtime tsan
runtime library thinks that all bad,
and we have data race, I report problem here:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68338
and looks like it fixed in trunk, but not in current stable release of gcc - 5.2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With