2014-12-13 8 views
7

ho scritto il seguente esempio giocattolo:di GCC riporta una gara di dati con un filo di sicurezza statica locale

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

Secondo lo standard C++ 11, questo non dovrebbe contenere alcuna gara di dati (cfr this post) .

Tuttavia TSAN con gcc 4.9.2, riporta una gara di dati:

================== 
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&) 
================== 

Cosa c'è di sbagliato qui?

  • è un buggy di TSan? (Quando utilizzo la toolchain di Clang, non ottengo alcun rapporto di gara dati)
  • GCC emette il codice che non è thread-safe? (Non sto usando -fno-threadsafe-statics però)
  • la mia comprensione dei locals statici è errata?
+0

https: //gcc.gnu.org/sulinedocs/libstdC++/manual/debug.html potrebbe essere necessario ricostruire la libreria standard. (Non ho guardato il tuo codice quindi potrebbe non essere correlato) –

+0

Secondo il duplicato (http://stackoverflow.com/questions/42062557/c-multithreading-is-initialization-of-a-local-static-lambda -thread-safe? noredirect = 1), questo problema è stato risolto a volte tra gcc 5.4 e gcc 6.3. –

risposta

2

è un buggy TSan? (Quando utilizzo la toolchain di Clang, non ottengo alcun rapporto di gara dati) il codice di GCC emette un codice che non è thread-safe? (Non sto usando -fno-threadsafe-> statics però) la mia comprensione dei locals statici è errata?

Credo che questo sia un errore nella parte gcc che genera il codice per gli scopi tsan.

provo questo:

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

Se sguardo al codice di generare da clang e gcc, tutto il bene, __cxa_guard_acquire viene chiamato in entrambi i casi per percorso init variabile locale statica. Ma in caso di controllo che abbiamo bisogno di init msg o non abbiamo problemi.

Il codice simile a questo

if (atomic_flag/*uint8_t*/) { 
    lock(); 
    call_constructor_of_msg(); 
    unlock(); 
} 

in caso di clangcallq __tsan_atomic8_load è stato generato, ma nel caso di gcc esso generare callq __tsan_read1. Nota: che questa chiamata annota le operazioni di memoria reale, non esegue le operazioni da solo.

quindi è in fase di esecuzione tsan libreria di runtime pensa che tutti i cattivi, e abbiamo corsa dati, ho Segnala un problema qui:

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

e sembra che risolto in tronco, ma non in stabile attuale versione di gcc - 5.2

-4

Se t1 raggiunge prima getMap(), quindi t2 bloccherà fino a quando viene restituito getMap() di t1. Quindi t2 riprenderà e salta l' l'intera dichiarazione di assegnazione (non chiamando getMap()) perché la mappa era già inizializzata, quindi sia t1 sia t2 useranno la stessa mappa. Da qui la "collisione".

+0

Erm, quanto sopra è il caso se usi -fno-threadsafe-statics. Altrimenti ci sono ancora più problemi con il codice. –

+1

Si noti che dopo che la mappa è stata inizializzata, t1 e t2 stanno solo leggendo la mappa (che è ok secondo lo standard). – Arnaud

+0

Oh, vero, hai solo accesso di lettura. Dovresti dichiararlo nella domanda originale ... ma sì, non riesco a trovare alcun bug nel codice, quindi. –

Problemi correlati