2017-01-31 1 views
14
#include <iostream> 
#include <sstream> 
#include <thread> 

using namespace std; 

int main() 
{ 
    auto runner = []() { 
     ostringstream oss; 
     for (int i=0; i<100000; ++i) 
      oss << i; 
    }; 

    thread t1(runner), t2(runner); 
    t1.join(); t2.join(); 
} 

Compilez le code ci-dessus dans g ++ 6.2.1, puis exécutez-le avec valgrind --tool=helgrind ./a.out. Helgrind se plaindraient:L'opérateur << (ostream &, obj) sur deux flux différents est-il sûr?

==5541== ---------------------------------------------------------------- 
==5541== 
==5541== Possible data race during read of size 1 at 0x51C30B9 by thread #3 
==5541== Locks held: none 
==5541== at 0x4F500CB: widen (locale_facets.h:875) 
==5541== by 0x4F500CB: widen (basic_ios.h:450) 
==5541== by 0x4F500CB: fill (basic_ios.h:374) 
==5541== by 0x4F500CB: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:73) 
==5541== by 0x400CD0: main::{lambda()#1}::operator()() const (43.cpp:12) 
==5541== by 0x4011F7: void std::_Bind_simple<main::{lambda()#1}()>::_M_invoke<>(std::_Index_tuple<>) (functional:1391) 
==5541== by 0x401194: std::_Bind_simple<main::{lambda()#1}()>::operator()() (functional:1380) 
==5541== by 0x401173: std::thread::_State_impl<std::_Bind_simple<main::{lambda()#1}()> >::_M_run() (thread:197) 
==5541== by 0x4EF858E: execute_native_thread_routine (thread.cc:83) 
==5541== by 0x4C31A04: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so) 
==5541== by 0x56E7453: start_thread (in /usr/lib/libpthread-2.24.so) 
==5541== by 0x59E57DE: clone (in /usr/lib/libc-2.24.so) 
==5541== 
==5541== This conflicts with a previous write of size 8 by thread #2 
==5541== Locks held: none 
==5541== at 0x4EF3B1F: do_widen (locale_facets.h:1107) 
==5541== by 0x4EF3B1F: std::ctype<char>::_M_widen_init() const (ctype.cc:94) 
==5541== by 0x4F501B7: widen (locale_facets.h:876) 
==5541== by 0x4F501B7: widen (basic_ios.h:450) 
==5541== by 0x4F501B7: fill (basic_ios.h:374) 
==5541== by 0x4F501B7: std::ostream& std::ostream::_M_insert<long>(long) (ostream.tcc:73) 
==5541== by 0x400CD0: main::{lambda()#1}::operator()() const (43.cpp:12) 
==5541== by 0x4011F7: void std::_Bind_simple<main::{lambda()#1}()>::_M_invoke<>(std::_Index_tuple<>) (functional:1391) 
==5541== by 0x401194: std::_Bind_simple<main::{lambda()#1}()>::operator()() (functional:1380) 
==5541== by 0x401173: std::thread::_State_impl<std::_Bind_simple<main::{lambda()#1}()> >::_M_run() (thread:197) 
==5541== by 0x4EF858E: execute_native_thread_routine (thread.cc:83) 
==5541== by 0x4C31A04: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so) 
==5541== Address 0x51c30b9 is 89 bytes inside data symbol "_ZN12_GLOBAL__N_17ctype_cE" 

Il semble que les deux fils appelés locale_facet.h:widen qui a causé la race de données car il n'y a pas de synchronisation apparaît dans cette fonction, même si operator << est appelée sur deux objets différents ostringstream. Donc je me demandais si c'était vraiment une course de données ou juste un faux positif de helgrind.

+3

Quelle que soit la norme dit, ce * devrait * être threadsafe. –

+2

Les états standard * Accès concurrentiel à un objet de flux (27.8, 27.9), objet de flux de données (27.6) ou flux de bibliothèque C (27.9.2) par plusieurs threads peuvent entraîner une course de données (1.10) sauf indication contraire (27.4). [Note: Les courses de données entraînent un comportement indéfini (1.10). -end note] * Donc, je suppose que les flux utilisent un état global dans le back-end qui n'est pas synchronisé. Ou c'est un faux positif. Mon instinct dit que cela devrait être sûr. – NathanOliver

+2

Je pense que ceci devrait être sûr selon §17.6.5.9/2: * "Une fonction de bibliothèque standard C++ ne doit pas accéder directement ou indirectement aux objets (1.10) accessibles par des threads autres que le thread courant sauf si les objets sont directement ou indirectement les arguments de la fonction, y compris 'this'." * Donc, je dirais que c'est une implémentation non conforme ou un faux positif. –

Répondre

0

Pour 2 différents cours d'eau, il est sûr de fil :)

+2

Il serait bon de fournir des références à l'appui de la réponse –

1

Mise à jour: Je dois admettre que je ne lis pas entièrement la question avant de répondre, alors je pris sur moi de la recherche cela.

Le TL; DR

Il existe des variables membres mutables ici qui pourraient causer des conditions de course. ios a des variables statiques par locale, et ces variables statiques chargement paresseux (initialisé au premier besoin) des tables de recherche. Si vous souhaitez éviter les problèmes de concurrence, veillez à initialiser les paramètres régionaux avant de joindre les threads afin que toute opération de thread soit en lecture seule dans ces tables de recherche.

Les détails

Lorsqu'un flux est initialisé, il remplit un pointeur qui charge dans le ctype correct pour les paramètres régionaux (voir _M_ctype): https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/basic_ios.h#L273

L'erreur fait référence à: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/locale_facets.h#L875

Cela peut être une condition de concurrence si deux threads initialisent simultanément les mêmes paramètres régionaux.

Cela devrait être threadsafe (bien qu'il peut encore donner erreur):

// Force ctype to be initialized in the base thread before forking 
ostringstream dump; 
dump << 1; 

auto runner = []() { 
    ostringstream oss; 
    for (int i=0; i<100000; ++i) 
     oss << i; 
}; 

thread t1(runner), t2(runner); 
t1.join(); t2.join(); 
+0

Pourriez-vous donner quelques références? – lz96

+0

Dans un programme plus grand qui fait beaucoup de choses avant de forger des discussions, ce n'est pas susceptible de poser problème. – ymmyk

+0

Est-ce un défaut standard ou juste un bug dans libstdC++? – lz96