GCC的TSAN使用线程安全静态本地报告数据竞争

Arn*_*aud 7 c++ multithreading gcc thread-safety c++11

我写了以下玩具示例:

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;
}
Run Code Online (Sandbox Code Playgroud)

根据C++ 11标准,这不应该包含任何数据竞争(参见本文).

然而,TSAN与gcc 4.9.2,报告数据竞赛:

==================
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&)
==================
Run Code Online (Sandbox Code Playgroud)

这有什么不对?

  • TSan越野车?(当我使用Clang的工具链时,我没有获得数据竞赛报告)
  • GCC发出的代码不是线程安全的吗?(我不是在使用-fno-threadsafe-statics)
  • 我对静态本地人的理解是不正确的?

fgh*_*ghj 3

TSan 是越野车吗?(当我使用 Clang 的工具链时,我没有收到数据竞争报告)GCC 是否发出非线程安全的代码?(虽然我没有使用 -fno-threadsafe->statics )我对静态局部变量的理解是否正确?

我相信这是出于某种目的gcc生成代码的部分错误tsan

我试试这个:

#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();
}
Run Code Online (Sandbox Code Playgroud)

如果查看clang和生成的代码gcc,一切都很好, __cxa_guard_acquire在两种情况下都会调用初始化静态局部变量的路径。但如果检查我们是否需要 init,msg我们就会遇到问题。

代码看起来像这样

if (atomic_flag/*uint8_t*/) {
  lock();
  call_constructor_of_msg();
  unlock();
}
Run Code Online (Sandbox Code Playgroud)

在 的情况下clang callq __tsan_atomic8_load生成,但在gcc它的情况下生成callq __tsan_read1. 请注意,此调用注释实际内存操作,而不是自行执行操作。

所以它在运行时tsan运行时库认为一切都不好,并且我们有数据竞争,我在这里报告问题:

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68338

看起来它已固定在主干中,但不在当前稳定版本的 gcc - 5.2 中