2015-01-01 19 views
16

Podczas pracy z odkażaczem nici Clanga zauważyliśmy ostrzeżenia o wyścigach danych. Uważamy, że jest to spowodowane techniką kopiowania na piśmie std :: string, która nie jest bezpieczna dla wątków, ale możemy się mylić. Zredukowaliśmy ostrzeżenie byliśmy zobaczyć, aby ten kod:Ostrzeżenie odkażania wątku Clang podczas korzystania ze std :: string w środowisku wielowątkowym

void test3() { 
    std::unique_ptr<std::thread> thread; 

    { 
    auto output = make_shared<string>(); 
    std::string str = "test"; 
    thread.reset(new std::thread([str, output]() { *output += str; })); 
    // The str string now goes out of scope but due to COW 
    // the captured string may not have the copy of the content yet. 
    } 

    thread->join(); 
} 

Kiedy skompilowany z gwintem odkażającego włączona:

clang++ -stdlib=libc++ -std=c++11 -O0 -g -fsanitize=thread -lpthread -o test main.cpp 

lub

clang++ -std=c++11 -O0 -g -fsanitize=thread -lpthread -o test main.cpp 

A kiedy uruchomić wiele razy, że w końcu daje to ostrzeżenie:

WARNING: ThreadSanitizer: data race (pid=30829) 
    Write of size 8 at 0x7d0c0000bef8 by thread T62: 
    #0 operator delete(void*) <null>:0 
    ... 

    Previous write of size 1 at 0x7d0c0000befd by thread T5: 
    #0 std::__1::char_traits<char>::assign(char&, char const&) string:639 
    ... 

Czy jest to fałszywy pozytyw od odkażacza nici czy jest to prawdziwy wyścig danych? Jeśli później, można go obejść bez zmiany kodu (na przykład przez przekazanie niektórych flag do kompilatora), czy jest to znany błąd w impedancji łańcuchowej (lub coś innego)?

UPDATE: dzyń --version wyjścia:

Ubuntu clang version 3.5-1ubuntu1 (trunk) (based on LLVM 3.5) 
Target: x86_64-pc-linux-gnu 
Thread model: posix 

UPDATE: The cpp użyć do odtworzenia tego ostrzeżenia.

+4

libC++ nie powinien używać ciągów COW, więc byłbym zaskoczony, gdyby to były ciągi COW. – hvd

+0

@BartoszKP, tak, przepraszam, ten problem z KOW był po prostu naszym domysłem popartym rozumowaniem w teście. Zasadniczo potrzebujemy, aby wątek kodu był bezpieczny, problem polega na tym, że czasami otrzymujemy podobne ostrzeżenia od wewnątrz, np. boost :: asio, więc nie zawsze jesteśmy w stanie zmienić kod. –

+0

Odkażanie nici clang jest świetnym narzędziem, ale bardzo trudno jest poprawnie zinterpretować wszystkie rodzaje muteksów i inne techniki synchronizacji. Tak może być w tym przypadku - otrzymałem od niego fałszywe alarmy, ponieważ na przykład nie rozumieją muteksów Qt. – BartoszKP

Odpowiedz

2

[Edytuj] Założenia poniżej okazują się być wadliwe, patrz link w komentarzach. T5, nie T62 to wątek zrodzony w powyższym kodzie.

Przydałoby się zrozumieć identyfikator wątku, ale zakładam, że T5 jest głównym wątkiem, a T62 jest wątkiem odrodzonym. Wygląda na to, że kopia jest wykonana na głównym wątku (zanim nowy wątek zostanie spwanowany) i zniszczona w nowym wątku (oczywiście). Jest to bezpieczne, ponieważ nowy wątek nie może się ścigać z głównym wątkiem, zanim on istnieje.

W związku z tym jest to błąd dezynfekcji nici. Nie udało się sprawdzić, czy wątek T62 istniał w czasie poprzedniego zapisu.

+0

Tak, wątek T5 wydaje się być tym, który uruchomił funkcję test3, a T62 tym, który wykonuje lambdę. [Tutaj jest pełne ostrzeżenie TSan] (https://gist.github.com/inetic/b73a8a0f4186c8fe034d#file-thread_sanitizer_warning-output). Wygląda na to, że masz rację, jeśli nie masz nic przeciwko, dam ci to pytanie jeszcze trochę przed zaznaczeniem odpowiedzi. –

+0

To ostrzeżenie jest dość skomplikowane. Z tym pełnym kontekstem, jest oczywiste, że ani T5, ani T62 nie są głównymi wątkami (!) Zamiast tego, wydaje się, że masz 'test2()', które zdarzyło się używać tej samej pamięci. – MSalters

+0

[tutaj] (https://gist.github.com/inetic/b73a8a0f4186c8fe034d#file-thread_sanitizer_warning-cpp) to kod cpp użyty do wygenerowania ostrzeżenia (numery linii nie pasują, przepraszam). Masz rację, że ani T5, ani T62 nie są głównymi wątkami, uruchamiam funkcję test3 wiele razy w osobnych wątkach, aby szybciej odtworzyć ostrzeżenie. Chociaż nie ma wspólnej pamięci między każdym wywołaniem test3, więc nie sądzę, że jest duża różnica (?). –

1

Jest to dość podchwytliwe. Mam podsumował logiki w kodzie poniżej:

  
    In thread T62: 
     Create string s (with reference count) 
     Create output_1 pointing to s in the thread storage for T62 
     Create thread T5 
     Create output_2 pointing to s in the thread storage for T5 
    Sync point 
    In thread T5: 
     Append to s ** MODIFY ** 
     Thread-safe decrement of reference count for s (not a sync point) 
     End of output_2 lifetime 
     Exit 
    In thread T62: 
     Thread-safe decrement of reference count for s (not a sync point) 
     End of output_1 lifetime 
     Deallocate s ** MODIFY ** 
     Join 
    Sync point 
    In thread T62: 
     Destroy T5 

miarę mogę powiedzieć, standard nie udziela żadnych gwarancji dotyczących synchronizacji w odniesieniu do wywoływania shared_ptr Deleter:

(20.8.2.2/4) W celu określenia obecności wyścigu danych, funkcje członkowskie mają dostęp do samych obiektów shared_ptr i weak_ptr, a nie do obiektów, do których się odnoszą.

biorę to oznaczać, że wszelkie modyfikacje, które faktycznie stanie z wskazywanego obiektu podczas wywoływania funkcji Członka shared_ptr, takie jak wszelkie modyfikacjach Deleter może sprawiają, są uważane za objęte zakresem shared_ptr, a zatem nie jest obowiązkiem shared_ptr, aby upewnić się, że nie wprowadzają wyścig danych. Na przykład, modyfikacje wprowadzone przez T5 w ciągu znaków mogą nie być widoczne dla T62 przez wątek czasu, który T62 próbuje zniszczyć.

Jednak Herb Sutter, w jego „Atomic <> Broń” talk, wskazany widział ją jako błąd mieć ubytek atomową liczenia odniesienia w shared_ptr destructor bez obu nabywać i uwolnienia semantyki, ale jestem nie wiem, w jaki sposób narusza standard.

+0

Jestem nieco zagubiony tutaj. Czy to nie działa po 'thread-> join()'? IOW, czy nadal istnieje wątek do wyścigu? – MSalters

+0

@MSalters: Niekoniecznie. Wątek mógł się zakończyć przed zakończeniem łączenia. Połączenie czeka tylko do momentu, w którym wątek zostanie zakończony, jeśli nie został już zakończony. –

+0

Dobra, teraz rozumiem. To wyjaśnia twój scenariusz. Zgadzam się, to prawdopodobnie odpowiednie wyjaśnienie. – MSalters

Powiązane problemy