Wo ist das Rennen in diesem Thread Sanitzer Warnung?

8

Der folgende Code erzeugt eine Warnung, wenn er mit Thread Sanitizer unter macOS ausgeführt wird. Ich kann nicht sehen, wo das Rennen ist. Der Steuerblock von shared_ptr und schwach_ptr ist threadsicher, und das Drücken und Herausspringen von wird durch Halten std::queueeiner Sperre erfolgen.

#include <future>
#include <memory>
#include <queue>

class Foo {
public:
  Foo() {
    fut = std::async(std::launch::async, [this] {
      while (!shouldStop) {
        std::scoped_lock lock(mut);
        while (!requests.empty()) {
          std::weak_ptr<float> requestData = requests.front();
          requests.pop();
          (void)requestData;
        }
      }
    });
  }

  ~Foo() {
    shouldStop.store(true);
    fut.get();
  }

  void add(const std::weak_ptr<float> subscriber) {
    std::scoped_lock lock(mut);
    requests.push(subscriber);
  }

private:
  std::atomic<bool> shouldStop = false;
  std::future<void> fut;
  std::queue<std::weak_ptr<float>> requests;
  std::mutex mut;
};

int main() {
  Foo foo;

  int numIterations = 100000;

  while (--numIterations) {
    auto subscriber = std::make_shared<float>();

    foo.add(subscriber);
    subscriber.reset();
  }

  std::this_thread::sleep_for(std::chrono::seconds(1));
}

Warnung mit Stacktrace:

WARNING: ThreadSanitizer: data race (pid=11176)
  Write of size 8 at 0x7b0800000368 by thread T1 (mutexes: write M16):
    #0 operator delete(void*) <null>:1062032 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x4f225)
    #1 std::__1::__shared_ptr_emplace<float, std::__1::allocator<float> >::__on_zero_shared_weak() new:272 (minimal:x86_64+0x1000113de)
    #2 std::__1::weak_ptr<float>::~weak_ptr() memory:5148 (minimal:x86_64+0x100010762)
    #3 std::__1::weak_ptr<float>::~weak_ptr() memory:5146 (minimal:x86_64+0x100002448)
    #4 Foo::Foo()::'lambda'()::operator()() const minimal_race.cpp:15 (minimal:x86_64+0x10000576e)
    #5 void std::__1::__async_func<Foo::Foo()::'lambda'()>::__execute<>(std::__1::__tuple_indices<>) type_traits:4345 (minimal:x86_64+0x1000052f0)
    #6 std::__1::__async_func<Foo::Foo()::'lambda'()>::operator()() future:2323 (minimal:x86_64+0x100005268)
    #7 std::__1::__async_assoc_state<void, std::__1::__async_func<Foo::Foo()::'lambda'()> >::__execute() future:1040 (minimal:x86_64+0x100005119)
    #8 void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (std::__1::__async_assoc_state<void, std::__1::__async_func<Foo::Foo()::'lambda'()> >::*)(), std::__1::__async_assoc_state<void, std::__1::__async_func<Foo::Foo()::'lambda'()> >*> >(void*) type_traits:4286 (minimal:x86_64+0x10000717c)

  Previous atomic write of size 8 at 0x7b0800000368 by main thread:
    #0 __tsan_atomic64_fetch_add <null>:1062032 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x24cdd)
    #1 std::__1::shared_ptr<float>::~shared_ptr() memory:3472 (minimal:x86_64+0x1000114d4)
    #2 std::__1::shared_ptr<float>::~shared_ptr() memory:4502 (minimal:x86_64+0x100002488)
    #3 main memory:4639 (minimal:x86_64+0x10000210b)

SUMMARY: ThreadSanitizer: data race new:272 in std::__1::__shared_ptr_emplace<float, std::__1::allocator<float> >::__on_zero_shared_weak()

edit: Ich kompiliere es mit:

clang++ -std=c++17 -g -fsanitize=thread -o test  minimal_race.cpp

Clang-Version:

$ clang++ --version
clang version 7.1.0 (tags/RELEASE_710/final)
Target: x86_64-apple-darwin18.7.0
Thread model: posix
InstalledDir: /usr/local/opt/llvm@7/bin

Ich benutze macOS 10.14.6

tuple_cat
quelle
Ich denke , das gehört in Codereview
xception
2
Nein, hier ist es gut. Es gibt ein Problem mit dem OP-Code, sie versuchen nicht nur, ihn zu verbessern. Eine Rennbedingung ist ein sehr ernstes Problem
Tas
habe es ein paar mal mit thread sanitizer mit gcc und clang unter linux versucht und konnte das
problem
Würde dies nicht zu einem möglichen Deadlock führen? Mit std::launch::asynckönnen Sie std::asyncfestlegen, wie Ihre Anfragen gemäß cppreference.com geplant werden sollen . Dies bedeutet , dass möglicherweise bei Fooaufgebaut ist, die futureden Mutex sperrt (was es nicht entsperren tut bis shouldStopwahr ist ). Wenn Foo::Addes dann aufgerufen wird, wird es versuchen, den Mutex zu sperren, und darauf warten, dass die Zukunft ihn entsperrt, was es niemals tut.
xEric_xD
1
hat es geschafft, eine bessere Spur mit libc ++ unter Clang unter Linux zu bekommen ... Ich weiß nicht, wie ich es hier posten soll, damit du es in diesem Link sehen kannst, stattdessen kompiliert mit clang++ -std=c++17 -O0 -ggdb -fsanitize=thread -stdlib=libc++ -o x x.cpp, passiert nicht mit libstdc ++,
xception

Antworten:

1

Ich denke, dies könnte ein Fehler mit libc ++ sein und in ihrer Implementierung für schwaches_ptr / gemeinsames_ptr klirren ...

Durch Ändern der Warteschlange für schwache_PTRs in eine gemeinsam genutzte_PTR-Warteschlange wird das Problem auch bei alten Clang-Versionen behoben.

Änderungen sind Zeile 25:

  void add(const std::shared_ptr<float> subscriber) {

Zeile 33:

  std::queue<std::shared_ptr<float>> requests;

und das während in Zeile 42 umschreiben als:

  while (--numIterations) {
    auto subscriber = std::make_shared<float>();

    foo.add(move(subscriber));
  }
xception
quelle
Konnten Sie es reproduzieren? Welche Clang-Version?
Marek R
Für mich klingt das logisch, da dies verhindert, dass die Objekte zerstört werden. Die Racebedingung scheint im Destruktor von aufzutreten shared_ptr.
JHBonarius
clang ++ -v clang Version 9.0.0 (tags / RELEASE_900 / final) Ziel: x86_64-pc-linux-gnu Thread-Modell: posix InstalledDir: / usr / lib / llvm / 9 / bin Ausgewählte GCC-Installation: / usr / lib / gcc /x86_64-pc-linux-gnu/9.2.0 Kandidat Multilib:.; @ m64 Kandidat Multilib: 32; @ m32 Ausgewählte Multilib:.; @ m64 @MarekR
Ausnahme
@MarekR Ich habe sogar meine eigene Rückverfolgung mit einem Link dazu in einem Kommentar zu der Frage gepostet und Flaggen erstellt
Ausnahme
1
@JHBonarius Wenn ich richtig verstehe, wie schwache_ptr / shared_ptr-Interaktionen funktionieren sollten, wenn sie aus verschiedenen Threads verwendet werden, sollten sie sicher sein (nicht die spitzen Daten, nur die Wrapper-Klassen), zumindest darin, wie das OP sie mit einem shared_ptr und einem schwachen_ptr verwendet (wobei es sich um verschiedene Objekte handelt) )
Ausnahme