2017-01-31 17 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(); 
} 

Skompiluj powyższy kod w g ++ 6.2.1, a następnie uruchom go za pomocą valgrind --tool=helgrind ./a.out. Helgrind skarżą:Czy operator << (ostream &, obj) na dwóch różnych strumieniach jest bezpieczny?

==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" 

Wydaje się, że obie nici zwane locale_facet.h:widen co spowodowało wyścig danych, ponieważ nie pojawia się brak synchronizacji w tej funkcji, choć operator << nazywa się na dwóch różnych ostringstream obiektu. Tak więc zastanawiałem się, czy to naprawdę wyścig danych, czy tylko fałszywy pozytyw od helgrind.

+3

Cokolwiek średnia mówi, to * powinny * być threadsafe. –

+2

Standardowy stan * Równoczesny dostęp do obiektu strumienia (27.8, 27.9), obiektu bufora strumieniowego (27.6) lub strumienia biblioteki C (27.9.2) przez wiele wątków może spowodować wyścig danych (1.10), chyba że określono inaczej (27,4). [Uwaga: Wyścigi danych powodują niezdefiniowane zachowanie (1.10). -end note] * Domyślam się, że strumienie używają jakiegoś globalnego stanu na zapleczu, który nie jest zsynchronizowany. Lub jest to fałszywy pozytyw. Moje jelito mówi, że powinno być bezpiecznie. – NathanOliver

+2

Myślę, że powinno to być bezpieczne zgodnie z paragrafem 17.6.5.9/2: * "Funkcja biblioteki standardowej C++ nie może bezpośrednio lub pośrednio uzyskać dostępu do obiektów (1.10) dostępnych przez wątki inne niż bieżący wątek, chyba że dostęp do obiektów odbywa się bezpośrednio lub pośrednio poprzez argumenty funkcji, w tym "this". * Tak więc powiedziałbym, że jest to niezgodna implementacja lub fałszywie pozytywna. –

Odpowiedz

0

dla 2 różnych strumieni jest to bezpieczne wątek :)

+2

Dobrze byłoby podać odniesienia wspierające odpowiedź –

1

Aktualizacja: Przyznaję, że nie w pełni odczytać pytanie przed udzieleniem odpowiedzi, więc wziąłem to na siebie, to na badania.

TL; DR

Tam są zmienne zmienne tutaj członkowskie, które mogłyby spowodować stan wyścigu. ios ma zmienne statyczne dla ustawień narodowych, a te zmienne statyczne są leniwym ładowaniem (inicjowane, gdy jest to potrzebne) tabelami wyszukiwania. Jeśli chcesz uniknąć problemów związanych z współbieżnością, po prostu pamiętaj, aby zainicjować ustawienia narodowe przed dołączeniem do wątków, aby każda operacja wątku była tylko do odczytu dla tych tabel odnośników.

Details

Kiedy strumień jest inicjowany go zapełnia wskaźnik, który ładuje się w odpowiednim ctype dla lokalizacji (patrz _M_ctype): https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/basic_ios.h#L273

Błąd odnosi się do: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/locale_facets.h#L875

Może to być sytuacja wyścigowa, jeśli dwa wątki jednocześnie inicjują te same ustawienia narodowe.

ten powinien być threadsafe (choć nadal może dać błąd):

// 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

Czy możesz podać kilka referencji? – lz96

+0

W większym programie, który robi wiele rzeczy przed rozwidleniem wątków, nie jest to prawdopodobnie problem. – ymmyk

+0

Czy jest to standardowa wada, czy tylko błąd w libstdC++? – lz96

Powiązane problemy